Skip to content

The Case iterator

DRAFT

Perhaps the least-known map iterator is Case '. Functions derived by Case iterate through their arguments in a special case of scattered indexing.

Case takes a non-negative integor vector i as argument and derives a function of rank max[i]+1.

q)i:3 0 1
q)df:i'        /derived function

Above, derived function df has rank 4. Each argument of df must conform to i; that is, be a list of three items or an atom. List and double-flip the arguments to visualise this.

3-0-1-case.png

q)df["abc";99;("quick";"brown";"fox");42 43 44]
42
"b"
99

The result of df has the same length as i and consists of successive picks from the argument lists. Specifically, for r:df . args

r[j]  <==>  args . (i j;j)

So, above, r is the first, second and third items of (respectively) the fourth, first and second arguments.

What use is this?

Vector Condtional

Case is a more general form of the Vector Conditional ?.

Recall that Vector Conditional is a ternary with three conforming arguments, say x, y and z. The result is y with items replaced from z wherever boolean x is false.

q)?[01b;2#`true;2#`false]
`false`true
q)?[0b;`true;`false]
`false
q)?[1b;2#`true;2#`false]
`true`true`

The left (first) argument of Vector Conditional must be a boolean atom or vector. The argument of Case must be an integer vector.

q)0 1'[2#`true;2#`false]
`true`false

Cond

Case also resembles Cond. Cond is sometimes called the “ternary conditional”, which is slightly misleading, because Cond is not a function. Even though it returns a result, it is a control structure; so it is not applied to an argument list, but followed by an expression list. And this matters because all the items of an argument list are evaluated, but only the first item of the expression list is sure to be evaluated.

q)$[1b;2#`true;2#`false]
`true`true

Above, only 1b and 2#`true were evaluated, not 2#`false. The first expression must be an atom but need not be boolean; any integer-based type will do.

q)$["c"$0;2#`true;2#`false]
`false`false

Case statements

The name of Case suggests a resemblance to case statements in some other programming languages: a control structure that maps values of a variable to lists of expressions to evaluate.

switch(foo) {
case 'abc': 
    tom = 42;
    dick = 53; 
    harry = 7;
    break;
case 'def':
    tom = 17;
    dick = 99; 
    harry = 4;
    break;
default:
    tom = 27;
    dick = 19; 
    harry = 44;
}

In JavaScript the code lines for each case are quite arbitrary. But where, as above, the code blocks simply assign different values to the same names in each case, q can map succinctly:

(tom;dick;harry):(42 53 7;17 99 4;27 19 44) ("abc";"def")?foo

And if only a single variable is being assigned

switch(foo) {
case 'abc': 
    tom = 42;
    break;
case 'def':
    tom = 17;
    break;
default:
    tom = 27;
}

Then q is even more succinct.

tom:42 17 27 ("abc";"def")?foo

And if foo is a list, then so too is tom.

The Case iterator in q corresponds to a case statement inside a loop in another language, in which the values assigned in each case vary with each iteration of the loop.

Not a common construct!

Scattered indexing

Another way to visualise the operation of Case is as a special case of scattered indexing. The arguments of the derived function must conform to the left argument i of Case. They therefore correspond to the rows of a matrix M with count i columns. In the derived function i' each i[j] specifies M[i j;j].


Exercises

FIXME: Write answers

  1. Compare Case, Vector Conditional and Cond: describe how they are alike and how they differ.

  2. Write a ternary lambda case such that case[x;y;z] returns x'[y;z] for suitable arguments.

  3. Adapt case so that it also handles the Vector Conditional; i.e. for boolean x, case[x;y;z] returns ?[x;y;z].