4. Some Primitive Functions#

4.1. Definitions#

In APL, data is processed using what we call functions. It is important to distinguish between two types of functions:

  1. Primitive Functions:

    • they are part of the APL language;

    • they are represented by symbols like , and ; and

    • they cannot be overwritten or removed.

  2. User-Defined Functions:

    • as their name implies, they are written by the user;

    • they are represented by names, for example Average or Budget; and

    • they can be overwritten and removed.

APL has a very rich set of primitive functions. In this chapter, we will explore just a few of them; many others will follow in subsequent chapters.

In the introduction to this book, we mentioned that in traditional mathematics, some symbols can be used with a single argument or two arguments. For example, in the expression \(a = x \color{red}{-} y\) the minus sign means subtract, whereas in \(a = \color{red}{-}y\) the minus sign indicates the negation of \(y\).

The first form is called the “dyadic” use of the symbol. The second form is called the “monadic” use of the symbol.

It is the same in APL, where most of the symbols (functions) have a monadic and a dyadic meaning. For example, here obtains the shape of the 1 2 3 4 vector:

 1 2 3 4
4

Whereas in here changes the shape of the 1 2 3 4 vector to 2 2:

2 2  1 2 3 4
1 2 3 4

There is, however, a major difference. In traditional mathematics, the symbol representing a monadic function is sometimes placed before its argument (as the \(-\) in \(a = \color{red}{-}y\)), sometimes after it (as the \(!\) in \(a = y\color{red}{!}\)), sometimes on both sides (as the \(|\cdot|\) in \(a = \color{red}|y\color{red}|\)), and some other conventions may be found.

In APL, the symbol representing a monadic function is always placed before its argument, as the in ⍴var.

4.2. Some Scalar Dyadic Functions#

4.2.1. Definition and Examples#

Scalar dyadic functions are primitive functions which have the following properties:

  • they are dyadic (require an argument on both sides);

  • they work item by item (scalar by scalar);

  • they can work on two arrays of the same shape, in which case the result also has the same shape; and

  • they can work on one array of any shape, and a single value (a scalar or any one-item array), in which case the result has the same shape as the non-singleton array.

The four basic arithmetic functions, addition, subtraction, multiplication, and division are scalar dyadic functions. They apply themselves between each item of the left argument and the corresponding item of the right argument, like this:

5 3 2 9 + 2 6 8 4
7 9 10 13

The function is applied between each item of two 4-item vectors. The result is also a 4-item vector.

As an example of a function that is not a scalar function, let us look at the reshape function. There is nothing in common between the shapes of its arguments:

2 3  6 8 2 1 9 3
6 8 2 1 9 3

In fact, the left argument has 2 items, the right one has 6 and the result in this case is a matrix.

Let us explore the behaviour of the basic arithmetic functions on vectors:

5 3 2 9 - 2 6 8 4
3 ¯3 ¯6 5
5 3 2 9 ÷ 2 6 4 7
2.5 0.5 0.5 1.28571
price  5.2 11.5 3.6 4 8.45
qty  2 1 3 6 2
costs  price × qty
costs
10.4 11.5 10.8 24 16.9

Scalar dyadic functions apply to arrays of any rank and shape.

As we saw in the introduction, a Sales Director makes forecasts for sales of 4 products over the coming 6 months, and assigns them to the variable forecast:

⎕RL  73
forecast  10×?4 655
forecast
90 160 420 500 20 30 110 450 170 370 290 360 340 190 320 120 510 370 150 460 240 520 490 280

At the end of the 6 months, they record the actual values in the variable actual. Here they are:

⎕RL  73
actual  forecast + ¯10+?4 620
actual
89 166 420 508 12 23 111 453 177 365 284 352 349 192 329 115 515 374 160 467 234 519 485 283

Remark

We initialise the forecast and actual variables with some random values by the use of the function roll ?. Notice this assignment is easier to type than some predefined set of values and we can use ⎕RL to always get the same result. You can learn more about ? and ⎕RL later on in the book.

The first thing any self-respecting Sales Director will want to know is the difference between the expected and the actual results. This can be done easily by typing:

actual - forecast
¯1 6 0 8 ¯8 ¯7 1 3 7 ¯5 ¯6 ¯8 9 2 9 ¯5 5 4 10 7 ¯6 ¯1 ¯5 3

Notice how subtracting two matrices gives a matrix of the same shape (recall that negative values are indicated by a high minus sign).

But remember, a scalar dyadic function may also be applied between a single value and an array of any shape.

For example, if we want to multiply forecast by 2, we can type:

forecast × 2        ⍝ same as 2 × forecast
180 320 840 1000 40 60 220 900 340 740 580 720 680 380 640 240 1020 740 300 920 480 1040 980 560

A complete list of scalar dyadic functions is given in Section 14.1.

4.2.2. Division By Zero#

An expression such as 17÷0 leads to an error message:

17÷0
DOMAIN ERROR: Divide by zero
      17÷0
        ∧

This happens because zero does not belong to the domain of valid denominators.

However, notice what 0÷0 returns:

0÷0
1

Despite being mathematically incorrect, the default behaviour gives the result 1, as given by the extension of the rule that “any number divided by itself should give 1”. Nevertheless, because this is sometimes inappropriate, it is possible to change the default behaviour (see Section 4.19.1).

4.2.3. Power#

In APL, the mathematical notation \(x^n\) is written x*n.

The function power (*) accepts any value(s) for n: integer or decimal, positive, negative, or zero, according to traditional usage.

To calculate the values of \(4^2\), \(4^{1.4}\), \(4^0\), \(\sqrt 4\), \(4^{-2.1}\), \(4^5\) we just need to type

4 * 2 1.4 0 0.5 ¯1 ¯2.1 5
16 6.9644 1 2 0.25 0.0544094 1024

0*0 gives 1, which is also not mathematically correct but a fairly useful convention to take.

There is no special symbol in APL to represent a square root; it is obtained by raising a value to the power \(\frac12\). If we take the square root of a negative number, then we get a complex number as a result:

¯1 * 0.5 
0J1

The subject of “complex numbers” pertains to mathematics, but Dyalog APL has some nice features related to complex numbers and so we will dive deeper into them later on.

4.2.4. Maximum & Minimum#

Maximum () and minimum () return respectively the larger of two values and the smaller of two values. Because they are scalar dyadic functions, they can be applied item by item between any two compatible arrays.

75  83
83
19  11 22 ¯20 60
19 22 19 60
52 14 ¯37 18.44  ¯60 15 ¯40 11.23
¯60 14 ¯40 11.23

Minimum can be used to apply a limit to the values in an array. For example, to set a ceiling of 450 in the matrix forecast, it is sufficient to type:

forecast  450
90 160 420 450 20 30 110 450 170 370 290 360 340 190 320 120 450 370 150 450 240 450 450 280

Notice how the larger values have been capped to 450.

4.2.5. Relationship#

As in traditional mathematics, APL provides the six relationship functions:

APL

Meaning

x < y

x less than y

x y

x less than or equal to y

x = y

x equal to y

x y

x greater than or equal to y

x > y

x greater than y

x y

x not equal to y

These symbols are obtained by pressing the APL key, simultaneously with the keys 3 to 8, respectively.

All these functions return 1 if the relation is true, or 0 if it is false.

11 < 7
0
24  24 11 33
1 0 1
5 = 9
0
3 8 7  5 8 0
0 1 1
6 > 2 37 2 9 3 6 4
0 1 0 1 0 1

The results are called binary, or Boolean, values (Boolean refers to the name of the mathematician George Boole). They can be processed in many different ways and are extremely useful, as we shall soon see.

Note that none of the four symbols < > can be applied to character arrays. Only = and can be used with character arrays, as illustrated below:

'm' = 'm'
1
'm' = 'M'
0
'k'  'a'
1
'sorry'  'r'
1 1 0 0 1

Because these functions are scalar dyadic functions, they are applied between individual scalars (the letters), not words:

'gold'  'gulf'
0 1 0 1

For the same reason, the two words (considered as vectors) must be of equal size, otherwise we get an error:

'male'  'female'
LENGTH ERROR: Mismatched left and right argument shapes
      'male'≠'female'
            ∧

4.2.6. Residue#

The residue function, represented by |, returns the remainder of a division.

In the expression R X|Y, R is the remainder of Y divided by X (be careful; the arguments of residue are given in the reverse order of that used by division Y÷X).

7 | 54
5
2 | 216 47 29 28        ⍝ Find even and odd numbers
0 1 1 0
X  7 4 11 ¯4.3 3 ¯5 6 ¯3
Y  54 84 119 19.6 29 43 ¯14 ¯14
X | Y
5 0 9 ¯1.9 2 ¯2 4 ¯2

The function can be used with negative and decimal values, as seen above.

The result R is always equal to Y - (N×X), where N is the largest possible integer such that R is always between 0 and X, but never equal to X. You can show that N is equal to ⌊(Y÷X), which means X|Y and Y - (X × (⌊(Y÷X))) give the exact same result:

(X|Y)
5 0 9 ¯1.9 2 ¯2 4 ¯2
(Y - (X × ((Y÷X))))
5 0 9 ¯1.9 2 ¯2 4 ¯2

Using a new dyadic primitive (obtained with APL+Shift+;) we can ask APL to check if the two arrays are the same, instead of having to compare each array item by item:

(X|Y)  (Y - (X × ((Y÷X))))
1

Keep reading the next section to see how works.

4.2.7. Array Comparison#

We have seen above that the dyadic functions = and are scalar, which meant we cannot use them when we wish to check if two arrays are the same or when we wish to check if two arrays are different:

1 2 3 4 = 1 2 3 4
1 1 1 1

The result above says the arrays have matching items, but doesn’t provide the summary information that the arrays are the same.

For a similar reason, we can’t even compare vectors of different lengths because dyadic scalar functions expect the shapes of their arguments to match:

'male' = 'female'
LENGTH ERROR: Mismatched left and right argument shapes
      'male'='female'
            ∧

Instead, we must use the function match () to check if two arrays are exactly the same:

1 2 3 4  1 2 3 4
1
'male'  'female'
0

The primitive not match () (obtained with APL+Shift+) is the counterpart to and checks if two arrays are different:

1 2 3 4  1 2 3 4
0
'male'  'female'
1

4.3. Order of Evaluation#

Like other programming languages, APL allows the programmer to use parentheses to specify the order of evaluation of a complex expression. Thus the expression 5×(6+7) means “add 6 to 7, then multiply by 5”. In the absence of parentheses, most other programming languages employ rules of precedence to decide how a complex expression such as 5×6+7 would be evaluated. Typically, the result will be 37 because multiplication is given precedence over addition and is performed first.

When APL was designed, it was decided that the sheer number of primitive functions meant that a set of precedence rules would be impossibly complex to remember and apply.

The solution adopted in APL is simple, and consistent with the rules we apply to calculate complex expressions in traditional algebra. Suppose, for example, that we need to calculate

\[\log \sin \sqrt {x ÷ 3}\]

To do this, we would first divide \(x\) by 3, then take the square root of the result, next calculate its sine, and finally calculate the logarithm: each function applies to the result of the entire expression to its right. This is how it is done in mathematics, and so it is in APL. The only difference is that in APL there are no exceptions!

To evaluate

5 × 6 + 7
65

we first calculate

6 + 7
13

and then multiply by 5, giving 65:

5 × 13
65

By the use of parentheses we can instruct APL to do the multiplication first,

(5 × 6) + 7
37

which an experienced APL programmer would probably write as

7 + 5 × 6
37

Rule

In an APL expression, each function takes as its right argument the result of the entire expression to its right. No functions have higher precedence than any others.

If the function is dyadic (takes both a left and a right argument), it takes as its left argument the array immediately to its left, delimited by the next function.

This is sometimes called “right to left evaluation” (although this is not strictly correct).

If necessary, one can use parentheses to force a different order of evaluation.

You must not be confused: each function is itself evaluated in its natural order, so 8÷4 gives 2, not 0.5! The expression “right to left” only means that the first operation executed is the rightmost one.

If the order of evaluation seems strange to you at first sight, just refer to a plain English sentence: “take the top half of the bottom quarter” does not mean “take the top half first, and then take the bottom quarter”; it means “first split into quarters and take the bottom one, then split that quarter into two halves and take the top half of it”: this is exactly the way that APL works! Even in everyday English language, which we write from left to right, we implicitly use the “right to left evaluation” rule.

Let us apply this rule to some examples:

3×5+1
18

First we sum,

5+1
6

then we multiply:

3×6
18

Now an example with three operations:

3 64+2 9>7
3 5

First we perform the “greater than” comparison,

2 9>7
0 1

then we add 4

4+0 1
4 5

and finally we take the minimum:

3 64 5
3 5

An even more complex example, from the end of the subsection on the primitive function residue (|):

(X|Y)Y-X×⌊Y÷X
1

Notice that in the example above we still kept a set of parentheses around X|Y, because we want to compare X|Y with Y-X×⌊Y÷X. To get rid of those parentheses one would need to introduce an intermediate variable, for example like so:

R  X|Y
RY-X×⌊Y÷X
1

Warning

In the beginning, you may encounter some surprises when using commutative functions.

For example, if v is a vector, 1+⍴v is different from ⍴v+1. Let us see why, with the following vector:

v  5 2 7

The value of

1+⍴v
4

is computed by first taking

v
3

followed by

1+3
4

Whereas the value of

v+1
3

is computed by first taking

v+1
6 3 8

followed by

6 3 8
3

which gives a different result.

This may be completely new to people who have experience with other programming languages, and is one of the reasons why we recommend that you do all of the exercises at the end of this chapter. With a little practice, you will soon find this simple rule very natural, and you will consider it a relief that you do not have to remember complex rules for function precedence.

4.4. Monadic Scalar Functions#

Most of the symbols we have encountered so far also have a monadic definition; let’s look at them now.

4.4.1. The Four Basic Symbols#

We will begin with the four basic symbols + - × ÷.

4.4.1.1. Conjugate#

The Plus sign used monadically is the function conjugate. It returns the complex conjugate of its arguments:

+ 0J1
0J¯1

When the numbers have no complex part, the function acts as the identity function, as the complex conjugate of a real number is the number itself:

+ 1 2 3 ¯3 6.5346 56J0
1 2 3 ¯3 6.5346 56

For historical compatibility reasons, monadic + also acts as the identity function if the argument is a character:

+ 'a'
a

Because it is a scalar function, it naturally works with mixed arrays:

+ 'B' 3 'a' ¯3 0J1 'g' 4J¯4
B 3 a ¯3 0J¯1 g 4J4

4.4.1.2. Negate#

The minus sign is the function negate. It returns the negation of its argument:

- 19 11 ¯33 0 ¯17
¯19 ¯11 33 0 17

4.4.1.3. Direction#

The multiply symbol used monadically is the function direction. For real numbers, it tells us the sign of its argument, using the following convention:

Result

Value

1

The value is positive

0

The value is zero

¯1

The value is negative

Some examples follow:

× 19 11 ¯33 0 ¯17
1 1 ¯1 0 ¯1

That is why the monadic multiply symbol is sometimes referred to as the sign function.

But direction also applies to complex numbers, and does so by extending the idea above. The direction of a number is that same number re-scaled so that it has magnitude (“length”) 1.

Let us take the complex number 3J4 as an example. If we look at the figure below, we see that the 3 is represented by the bottom side of the triangle and the 4 by the right side of the triangle. The little arrow that goes from the lower-left to the upper-right vertex of the triangle represents the direction in which 3J4 points.

_images/Direction_Right_Triangle.png

Fig. 4.1 A right triangle with sides 3, 4 and 5 representing the complex number 3J4.#

We can also see that said arrow has length 5, as the Pythagorean theorem tells us:

((3*2)+(4*2))*0.5
5

Now ×3J4 is the complex number associated with a triangle that is similar to the one above, but with the arrow measuring 1, exactly like the smaller triangle inside the larger triangle in the figure below:

_images/Direction_Right_Triangle_Scaled.png

Fig. 4.2 Scaled down version of the 3J4 triangle.#

We only have to figure out the lengths of the other two sides:

× 3J4
0.6J0.8

4.4.1.4. Reciprocal#

No surprise here, as the symbol divide gives the reciprocal or inverse of its argument:

÷ 2 ¯4 .3 .25 ¯7
0.5 ¯0.25 3.33333 4 ¯0.142857

4.4.2. Other Scalar Monadic Functions#

4.4.2.1. Magnitude (Absolute Value)#

The monadic stile | represents the absolute (unsigned) value of its argument, if its argument only contains real numbers:

V  29.2 49.3 ¯14.8 0 ¯37.2
|V
29.2 49.3 14.8 0 37.2

Like monadic ×, monadic | generalises nicely for complex numbers. Take the complex number 12J5 and its representation as a right triangle in the figure below.

_images/Magnitude_Right_Triangle.png

Fig. 4.3 The complex number 12J5 represented by a right triangle.#

|12J5 is the length that is missing in the figure. In this case, it is

|12J5
13

From a geometrical point of view it becomes clear that × and | are related for any number v: if we take the small triangle (×v) and we multiply it by the length of the larger side (|v) then we get the original triangle, which represents the original (complex) number:

v  0 1 ¯1 2.3 ¯4.6 0J1 3J¯4.2
v(×v)×|v
1

4.4.2.2. Exponential#

The expression *n gives \(e^n\), where \(e\) is the base of the natural logarithm, approximately \(2.71828\):

*1
2.71828
* 1 0 3 ¯1
2.71828 1 20.0855 0.367879

4.4.2.3. Floor and Ceiling#

Floor () rounds its argument down, while ceiling () rounds its argument up, to the nearest smaller or larger integer value, respectively:

v  51.384 48.962 0 12.5 ¯73.27 ¯9.99
v
51 48 0 12 ¯74 ¯10
v
52 49 0 13 ¯73 ¯9

To round a value to the nearest integer a commonly used method is to add 0.5 and then take the floor, or alternatively, to subtract 0.5 and take the ceiling, as shown here:

v+0.5
51 49 0 13 ¯73 ¯10
v-0.5
51 49 0 12 ¯73 ¯10

The results are the same, except when the decimal part of a number is 0.5. For example, 12.5 above gets rounded to 13 or 12 depending on which method is used.

4.5. Left and Right Tacks#

4.5.1. Same#

APL also includes two primitive functions whose monadic name is equal: same. These two primitives are the right tack () and the left tack (), obtained respectively with APL+\ and APL+Shift+\.

Their monadic name is “same” because they act as true identity functions, returning their argument completely unchanged:

 1 2 3
1 2 3
 'Banana'
Banana
 'Bag' 3 (1 2 'Bag') ('Bag' ('Bag' 3) (2 21 2 3 4))
┌───┬─┬─────────┬─────────────────┐ │Bag│3│┌─┬─┬───┐│┌───┬───────┬───┐│ │ │ ││1│2│Bag│││Bag│┌───┬─┐│1 2││ │ │ │└─┴─┴───┘││ ││Bag│3││3 4││ │ │ │ ││ │└───┴─┘│ ││ │ │ │ │└───┴───────┴───┘│ └───┴─┴─────────┴─────────────────┘

Exactly the same thing happens (i.e. nothing happens) if you replace all the above with , for example:

 'Bag' 3 (1 2 'Bag') ('Bag' ('Bag' 3) (2 21 2 3 4))
┌───┬─┬─────────┬─────────────────┐ │Bag│3│┌─┬─┬───┐│┌───┬───────┬───┐│ │ │ ││1│2│Bag│││Bag│┌───┬─┐│1 2││ │ │ │└─┴─┴───┘││ ││Bag│3││3 4││ │ │ │ ││ │└───┴─┘│ ││ │ │ │ │└───┴───────┴───┘│ └───┴─┴─────────┴─────────────────┘

These two functions may look useless, but they are not. For one, a neat little trick we can do with them is to display a value of a variable that we just assigned; for example, compare the two code cells below:

matrix  2 31 2 3 4 5 6        ⍝ Nothing gets displayed explicitly
matrix 2 31 2 3 4 5 6        ⍝ The value of matrix is displayed 
1 2 3 4 5 6
matrix 2 31 2 3 4 5 6        ⍝ The value of matrix is displayed
1 2 3 4 5 6

These functions become even more useful combined with the power of operators or tacit programming (explained in detail in future chapters), or if we take a look at their dyadic usages (explained next).

4.5.2. Dyadic Usage#

The left and right tacks differ in their dyadic usage, of course, otherwise there would be no point in having two primitive functions that behaved in exactly the same manner.

When used dyadically, the left tack returns its left argument and the right tack returns its right argument. If you ever forget which dyadic tack returns what, just remember that each tack returns the argument to which it is pointing:

1 2 3  'Bananas'
1 2 3
1 2 3  'Bananas'
Bananas

4.6. Processing Binary Data#

Remark

Binary values are most often produced by the comparison functions that we have already seen. However, the result of any function (such as addition or subtraction) which is composed only of 1s and 0s can be used as a binary (or Boolean) value, and may be used as an argument to any of the special primitive functions that apply to Boolean values.

Among the various ways of producing binary results, membership appears to be one of the most interesting tools.

4.6.1. Membership#

  • Membership tells whether the items of its left argument are present (1) or not (0) in the right argument, regardless of their position in it;

  • it accepts arguments of any shape or type; and

  • the result produced always has the same shape as the left argument.

Some examples will help you understand the function:

23 14 41 19  17 88 19 50 51 52 23 40
1 0 0 1

This means that 23 and 19 appear somewhere in the rightmost vector, whereas 14 and 41 do not. The left argument has 4 items, and so has the result.

The function membership can operate on arguments of completely different shape. For example, it is possible to detect the presence of each item of a vector in a matrix, or vice versa.

In an earlier chapter we used a matrix containing the first six months of the year:

monMat  6 8'January FebruaryMarch   April   May     June    '
January February March April May June

We can ask if certain letters are present in this matrix:

'December'  monMat
0 1 1 1 0 1 1 1

The result shows that all letters of the word “December” appear in monMat, except “D” and lowercase “m” (which should not be confused with the uppercase “M” of March and May).

In this case we used a vector left argument and a matrix right argument. Let’s try it the other way around. The following expression tells us which letters in the matrix monMat appear in the word “Century”:

monMat  'Century'
0 0 1 1 0 1 1 0 0 1 0 1 1 0 1 1 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 1 1 0 0 0 0

As you might imagine, any comparison between numbers and letters gives zero:

1952  '1952'
0
'1952'  1952
0 0 0 0

Remember that '1952' is a vector of four characters, none of which can be found in the number 1952.

We recommend that you do exercise 25 to further explore membership.

4.6.2. Binary Algebra#

Binary values can be processed using half a dozen specialised primitive functions, the main ones being and, or, xor, and not. Additional functions will be described in Section 4.19.

The function and is represented by the symbol (APL+0), as it is in mathematics. It returns the result 1 if the left and the right arguments are both equal to 1:

0  0
0
0  1
0
1  0
0
1  1
1

We can condense the four expressions above into a single one, given that is also a scalar function:

0 0 1 1  0 1 0 1
0 0 0 1

The function or is represented by the symbol (APL+9), as it is in mathematics. It returns the result 1 if the left or right argument is equal to 1.

The four possible cases are shown in the following expression:

0 0 1 1  0 1 0 1
0 1 1 1

Xor is an acronym for eXclusive Or. It returns the result 1 if one of the arguments is equal to 1, but not if both are equal to 1.

In automation, the same function is generally represented by a circled plus sign, like \(\oplus\).

APL does not need a different symbol for the function, because xor is the same as one of the comparison functions we have already met:

0 0 1 1  0 1 0 1
0 1 1 0

The last function is the monadic function not. Represented by the tilde ~ (APL+t), it converts 0 into 1 and 1 into 0:

~ 0 1 0 0 0 1 1
1 0 1 1 1 0 0

Remark

  • And, or, and xor are scalar dyadic functions;

  • Not is a scalar monadic function; and

  • Membership is a dyadic function, but it is not a scalar function.

All these functions can be applied to binary data of any shape. For example, let us see if any of those items of forecast, which are greater than 350 thousand euros, have been exceeded by actual sales:

bin  (forecast>350)  (actual>forecast)
0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 1 1 0 1 0 0 0 0

A side note: the parentheses around the rightmost expression (actual>forecast) are not strictly needed. However, they do no harm either, so we have added them here to help you read the expression, since you may not yet be fully familiar with APL’s order of evaluation.

4.6.3. Without#

Given a vector X and any array Y, the expression X~Y returns a vector equal to X, but in which all items of Y have been removed. The size and shape of Y is immaterial, only the individual items of Y are used.

This function is called without.

'This Winter is warm' ~ monMat
TsWtswm

Notice how the right argument above (monMat) is a matrix.

'Congratulations' ~ 'ceremony'
Cgatulatis

The uppercase “C” is preserved here because it is different from the lowercase “c”.

matrix
1 2 3 4 5 6
0 2 4 6 8 10 12 ~ matrix
0 8 10 12

Of course, it also works on numbers.

4.7. Processing Nested Arrays#

When working with nested arrays, it is important to recognise whether or not you are using a scalar function.

4.7.1. Scalar vs. Non-scalar Functions#

In Section 3, we set up a nested vector children, which is composed only of numeric items:

)copy DISPLAY
children  (6 2) (35 33 26 21) (7 7) 3 (19 14)
DISPLAY children
C:\Program Files\Dyalog\Dyalog APL-64 18.2 Unicode\ws\DISPLAY.dws saved Fri Mar 18 10:04:14 2022
┌→────────────────────────────────────┐ │ ┌→──┐ ┌→──────────┐ ┌→──┐ ┌→────┐ │ │ │6 2│ │35 33 26 21│ │7 7│ 3 │19 14│ │ │ └~──┘ └~──────────┘ └~──┘ └~────┘ │ └∊────────────────────────────────────┘

The application of scalar functions is straightforward.

For example, when we add 50 to children, the value 50 is added to each of the items of children. As these items are themselves scalars or vectors, adding 50 means adding 50 to each of their individual items. This process continues through all levels of nesting, ensuring that 50 gets added to all the individual items of children. The result is therefore structurally identical to children:

DISPLAY children + 50
┌→─────────────────────────────────────────┐ │ ┌→────┐ ┌→──────────┐ ┌→────┐ ┌→────┐ │ │ │56 52│ │85 83 76 71│ │57 57│ 53 │69 64│ │ │ └~────┘ └~──────────┘ └~────┘ └~────┘ │ └∊─────────────────────────────────────────┘

One way of expressing this behaviour is to say that the scalar functions (both the dyadic and monadic ones) permeate down through the structure of nested arrays, until they reach the lowest-level items, and then apply themselves at this level. They are said to be pervasive functions.

Non-scalar functions, like membership, are not pervasive.

what  19 (6 2) 3 (33 26)
what  children
0 1 1 0

The item (6 2) of what is also an item of children, hence membership gives the answer 1, and the same is true for the value 3.

In contrast, 19 is only an item of the fifth item of children, it is not an entire item of children. Because a non-pervasive function processes each item as a whole, 19 is not the same as (19 14), so the answer is 0. The same goes for (33 26), which is only part of the second item of children.

4.7.2. Be Careful With Shape/Type Compatibility#

It is easy to add a vector of five scalar items to children, because each of the five scalars can be added to the corresponding item of children:

children + 10 20 30 40 50
┌─────┬───────────┬─────┬──┬─────┐ │16 12│55 53 46 41│37 37│43│69 64│ └─────┴───────────┴─────┴──┴─────┘

But if we try to add a vector of five sub-vectors to children, we must ensure that the shape of each sub-vector is compatible with the shape of the corresponding item of children, for example

children + (4 8) (5 7 4 9) (1 ¯1) (100 200 500) (14 51)
┌─────┬───────────┬───┬───────────┬─────┐ │10 10│40 40 30 30│8 6│103 203 503│33 65│ └─────┴───────────┴───┴───────────┴─────┘

or

children + (4 8) 0 (1 ¯1) (100 200 500) ¯100
┌─────┬───────────┬───┬───────────┬───────┐ │10 10│35 33 26 21│8 6│103 203 503│¯81 ¯86│ └─────┴───────────┴───┴───────────┴───────┘

If there is any incompatibility, a LENGTH ERROR is issued:

children + (1 2)(2 3)(3 4)(4 5)(5 6)
LENGTH ERROR: Mismatched left and right argument shapes
      children+(1 2)(2 3)(3 4)(4 5)(5 6)
              ∧

All of the items of our vector could have been added to the corresponding items of children except the second one. APL has detected and signalled this error.

You must also be careful if a nested or mixed array contains character data; it will not be possible to apply any arithmetic function to the array as a whole.

4.7.3. Tally#

We have seen the dyadic functions match and not match , and now we will see how we can use their monadic versions to work with (nested) arrays.

We mentioned tally briefly in the first chapter, which is the monadic use of . Tally does exactly what its name suggests: it counts the amount of items an array has along its first dimension.

For a vector, this corresponds to its length:

 1 2 3 4 5 6
6
 'ui'
2

A nested vector is still a vector:

(4 5) 'a' 'Bag' (35 'Cat' 42)
┌───┬─┬───┬───────────┐ │4 5│a│Bag│┌──┬───┬──┐│ │ │ │ ││35│Cat│42││ │ │ │ │└──┴───┴──┘│ └───┴─┴───┴───────────┘
 (4 5) 'a' 'Bag' (35 'Cat' 42)
4

For higher dimensional arrays, tally returns the size of its first dimension:

 3 39 8 7 6 5 4 3 2 1
3
 2 5 270
2

For scalars, tally returns 1:

 3
1
 '3'
1

In case you don’t think it makes sense to have the tally of a scalar return 1, consider the sequence below:

 'abcd'
4
 'abc'
3
 'ab'
2
⍝≢ 'a'    ⍝ what should this give??
 ''
0

Putting things this way, it sure looks reasonable that 'a' give 1, right?

4.7.4. Depth#

Dyadic and are very closely related, but their monadic versions aren’t as much. The monadic function depth helps us work with nested arrays, in that it helps us count how many levels of nesting there are.

The result of is really simple to understand. First of all, the depth of a scalar is 0:

1
0
'a'
0
4525324
0

Then, the depth of an array is 1 larger than the depth of its deepest item, or sub-array. For example, a simple vector like 1 2 3 4 only has scalars as items, whose depths are 0, so the depth of the vector will be 1.

1 2 3 4
1

Now let us consider a nested vector composed of simple vectors like the one we just saw.

nested  (1 2 3 4) (3 5) (10 20 30)
┌───────┬───┬────────┐ │1 2 3 4│3 5│10 20 30│ └───────┴───┴────────┘

The items of nested are vectors of depth 1, so the depth of nested should be 2:

nested
2

has one more thing to it: if the nesting of the sub-arrays is not uniform then the result will be negative.

For example, 1 2 3 4 has depth 1 and 42 has depth 0, so a vector composed of these two sub-arrays has depth -2:

(1 2 3 4) 42
¯2

4.8. Reduce#

4.8.1. Presentation#

A few pages ago we calculated the costs of some purchased goods:

costs  price × qty
10.4 11.5 10.8 24 16.9

How much did we spend?

10.4 + 11.5 + 10.8 + 24 + 16.9
73.6

But writing things out like this is cumbersome and depends on us looking at the scalars in the costs vector. What if the price or the qty changes?

Mathematicians are creative people who long ago devised the symbol \(\sum\), always with a pretty collection of indices above and below, that is used to indicate the sum of some numbers. This symbol makes it complex to understand and is difficult to type on a keyboard.

In APL, the operation is written like this:

+/ costs
73.6

Simple, isn’t it? This expression gives the total of all the items in the vector. You can read this as “plus reduction” of the variable costs.

To gain a better understanding of the process:

When we write an expression such as

+/ 21 45 18 27 11
122

it works as if we had written

21 + 45 + 18 + 27 + 11
122

and we obtain the sum 122.

In fact, it works as if we had “inserted” the symbol + between the values. But then, if we write

×/ 21 45 18 27 11
5051970

it works as if we had written

21 × 45 × 18 × 27 × 11
5051970

so, we get the product 5051970.

Similarly, if we write

/ 21 45 18 27 11
45

it works as if we had written

21  45  18  27  11
45

so, we get the maximum 45. And so on for other functions we put to the left of /.

Exercise:

Try to evaluate the following expression in your head or with pen and paper: 23⌈ ⌈/ 17.81 21.41 9.34 16.53

Don’t panic! Remember to evaluate it symbol by symbol, from right to left.

4.8.2. Definition#

Reduce, represented by the symbol /, belongs to a special category of symbols called operators.

In most programming languages the word operator is used to describe operations like addition, subtraction, multiplication, and so on. In APL such operations are called functions; typical examples are +, -, × and . The word operator has a separate meaning in APL.

In APL, a function works on an array or between two arrays to produce a result:

price × qty
10.4 11.5 10.8 24 16.9

Whereas an operator applies to one or two operands (its “arguments”) to produce what we call a derived function. That is, after we use the operator on its operands, we get a (derived) function which may then be used with an array, or between two arrays, to produce a result:

+/
/ ┌─┘ +

This is the representation APL gives to the plus reduction, where the tree-like structure shows that + is an operand to /. We may then use this derived function with an array in order to get a result:

stock  +/ qty
14

In the expression above, the symbol / is the operator. It takes the function + as its single operand (similar to how we give arguments to functions) and produces the derived function +/. This derived function is then applied to qty, giving a result which is assigned to stock and displayed with .

Please note that the argument to a monadic function is always to the right of the function, whereas the function applied to a monadic operator (its operand) is always to the left of the operator.

All of the APL primitive dyadic functions may be used as the operand to reduce; you can even apply a dyadic user-defined function. This generality makes reduce, and other operators, extremely powerful.

Dyalog APL provides a total of around 20 such powerful operators, listed in Section 14.4. It is also possible to write your own operators, just as it is possible to write your own functions.

4.8.3. Reduction of Binary Data#

Among the typical usages of reduce are and applied to binary data.

  • ∧/ bin gives the result 1 if all the items of bin are equal to 1;

  • ∨/ bin gives the result 1 if at least one of the items of bin is equal to 1;

  • +/ bin tells us how many items of bin are equal to 1.

You can verify it on some small examples:

bin  1 1 1 0 1 0 1
1 1 1 0 1 0 1
/ bin
0
/ bin
1
+/ bin
5
allOnes  1bin
1 1 1 1 1 1 1
/ allOnes
1

Let us revisit a vector named contents (from Section 3):

contents  12 56 78 74 85 96 30 22 44 66 82 27
12 56 78 74 85 96 30 22 44 66 82 27

Now we will answer some questions about the values of the items of contents.

Are all the values greater than 20?

/ contents > 20
0

The answer is no.

Is there at least one value smaller than 30?

/ contents < 30
1

The answer is yes.

How many values are smaller than 30?

+/ contents < 30
3

The answer is 3.

4.8.4. Reduction of Nested Arrays#

When you apply a reduction to a nested array, you must check that the items of the nested array are compatible (in shape and type) with the function that you intend to apply:

+/ (4 8) (1 4) 10 (9 5)
┌─────┐ │24 27│ └─────┘

The expression above works because all 2-item vectors can be added together, and a single scalar (the 10) can be added to an array of any shape, because + is a scalar function.

Notice, however, that in the expression below the 3-item vector cannot be added to the other 2-item vectors, so APL reports an error:

+/ (4 8) (1 4) (1 2 3) (9 5)
LENGTH ERROR: Mismatched left and right argument shapes
      +/(4 8)(1 4)(1 2 3)(9 5)
      ∧

4.8.5. Reduction With Non-Commutative Functions#

Another thing to be careful about is the use of reduction with non-commutative functions, like - or ÷. Reducing an array by such a function yields results which may be counter-intuitive, but which may nevertheless be useful in a number of applications.

For example, remember that

-/ 45 9 11 2 5
50

is equivalent to

45 - 9 - 11 - 2 - 5
50

which, by APL’s order of evaluation is equivalent to:

45 - (9 - (11 - (2 - 5)))
50

If, instead, we use the traditional mathematical convention that interprets

\[45 - 9 - 11 - 2 - 5\]

as

\[(((45 - 9) - 11) - 2) - 5\]

we get the result 18.

This kind of “alternating series” can be useful for some calculations, although only rarely for business applications.

4.8.6. Application 1#

The employees of a company are divided into three hierarchical categories, denoted simply 1, 2 and 3. Two variables contain the salaries and the categories of these employees; we define them below and then use to show some of their initial values:

⎕RL  73
salaries  ?1005000
10salaries
2121 4778 4914 3139 4561 221 1252 1530 4475 332
⎕RL  73
categories  ?1003
10categories
1 2 2 2 3 3 1 1 1 1

We can see the salaries of the first three employees are, respectively, 2121, 4778 and 4914 (of some currency) and their respective categories are 1, 2 and 2.

With what we learned in Section 4.8.3 we can also find out how many employees belong in the third category:

+/ categories = 3
32

Now the employees ask for an increase in their salaries. Each category of employee requests a different percentage increase, as shown in the following table:

Category

Upgrade

1

8%

2

5%

3

2%

How much is that going to cost the company?

Let us just create a variable containing the three rates shown above:

rates  8 5 2 ÷ 100
0.08 0.05 0.02

The first employee is in category 1, so the rate that applies to this person is

rates[1]
0.08

More generally, the rates applied to all of the employees can easily be obtained with rates[categories]:

10rates[categories]
0.08 0.05 0.05 0.05 0.02 0.02 0.08 0.08 0.08 0.08

Having the rates, we only have to multiply them by the salaries to obtain the individual increases:

10 salaries × rates[categories]
169.68 238.9 245.7 156.95 91.22 4.42 100.16 122.4 358 26.56

Finally, by adding them all together, we discover how much it will cost the company:

+/ salaries × rates[categories]
12070.6

Note that:

  • the expression remains valid regardless of the number of employees or categories;

  • the result has been obtained without writing a program (no loops, no tests); and

  • this expression can be phrased in the simplest possible English, namely:

Sum the salaries multiplied by rates according to categories.

This illustrates how the implementation of a solution in APL can be very close to the way that the solution would be expressed in everyday language. It also shows the advantage of not having to deal with trivial and “irrelevant” matters such as looping, memory allocation, declarations, etc. before a working solution can be developed.

4.8.7. Application 2#

Imagine now that we want to calculate the average of a set of values, for example the values contained in the variable contents.

To do that, we must:

  • add all the values:

+/ contents
672
  • count how many values we have:

contents
12
  • divide one by the other:

(+/contents) ÷ (contents)
56

The result is 56.

Again, because of APL’s simple rule for the order of evaluation, the rightmost set of parentheses could be omitted.

4.9. Axis Specification#

4.9.1. Totals in an Array#

4.9.1.1. Processing Arrays#

We have seen the result of applying reduction to vectors, but what about matrices and higher rank arrays?

As an example, let us recall the array prod. Its 3 dimensions represent respectively:

  1.   5 years;

  2.   2 assembly lines;

  3.   12 months.

⎕RL  73
prod  ?5 2 1250
9 16 42 50 2 3 11 45 17 37 29 36 34 19 32 12 37 15 46 24 49 28 36 9 29 5 45 23 27 4 23 16 39 22 22 2 46 20 47 17 18 25 13 38 42 38 45 28 37 19 5 10 30 49 16 18 46 47 47 3 39 23 41 20 6 40 21 22 40 49 20 1 13 36 12 40 12 15 24 20 21 12 19 25 8 48 22 37 33 1 39 30 50 50 8 35 38 31 24 40 23 7 20 34 35 6 19 27 41 1 14 50 17 42 6 35 12 48 30 29

We can calculate totals along any of these 3 dimensions: years, lines and months.

We specify the dimension (or axis) between brackets after the reduce symbol:

+/[axis] prod

For example, suppose we want to calculate the total monthly production values over the 5 years. Years are represented by the 1st dimension of prod, so we write:

+/[1] prod
126 107 128 163 94 78 94 133 158 124 136 93 168 111 156 136 111 123 125 149 193 213 139 102

We obtain a 2 by 12 matrix, giving the production of the 2 assembly lines, month by month. If we were to divide this matrix by 5, we would get the average production for each month, per assembly line.

Now, let us add up the production numbers of the two assembly lines. Lines are represented by the 2nd dimension of prod, so we write:

+/[2] prod
43 35 74 62 39 18 57 69 66 65 65 45 75 25 92 40 45 29 36 54 81 60 67 30 76 42 46 30 36 89 37 40 86 96 67 4 21 84 34 77 45 16 63 50 71 62 27 60 79 32 38 90 40 49 26 69 47 54 49 56

We obtain a 5 by 12 matrix, with the total production of both assembly lines, month by month, in each of the 5 years.

And finally, let us calculate the annual production of each assembly line. Months are represented by the 3rd dimension of prod, so we write:

+/[3] prod
297 341 257 377 327 322 249 361 304 325

The result is a 5 by 2 matrix, in which the columns contain the annual production of the two assembly lines in each of the five years.

We shall learn more about axis in the “Operators” chapter; let us first explore another simple use of this operator.

Suppose that we would like to multiply each of the rows (or columns) of a matrix by different values; we can use axis to specify whether we multiply row-wise or column-wise. First, here is a matrix:

⎕RL  73
tam  ?3 59
9 2 2 3 1 5 4 2 3 3 5 8 4 1 4

Let us multiply row-wise:

tam×[1]5 2 10
45 10 10 15 5 10 8 4 6 6 50 80 40 10 40

And now column-wise:

tam×[2]2 5 0 2 1
18 10 0 6 1 10 20 0 6 3 10 40 0 2 4

4.9.1.2. Axis Is Like an Operator#

The dimension specified within brackets is the axis along which the function is applied.

This produces a derived function, and for this reason, the pair of axis brackets is often called the axis operator.

The syntax for axis does not quite follow the general syntax for operators, but it shares all other properties with genuine operators. Axis takes a function as its left operand (the derived function +/ in the last example above), the dimension specification as its “right operand” (3 in the example), and produces a derived function, which is applied to prod to calculate the annual sums.

Viewed as an operator, axis is therefore dyadic. It is, however, important to emphasise that its “right” operand is not prod, it is the expression within the brackets. This is the first example of an operator that takes an array as an operand. We will find some more as we explore operators later on.

4.9.2. The Shape of the Result#

The dimensions of prod are

prod
5 2 12

and we can see that the dimensions of +/[1]prod, +/[2]prod and +/[3]prod depend on this shape. In fact, when the axis is 1, the shape of the result is the original shape without the 1st item:

⍴+/[1]prod
2 12

When the axis is 2, the shape of the result is the original shape without the 2nd item:

⍴+/[2]prod
5 12

And when the axis is 3, the shape of the result is the original shape without the 3rd item:

⍴+/[3]prod
5 2

You can see that reduction of a 3D array gives a 2D array, in which the summed dimension has “disappeared”. This is the origin of the term “reduce”; it reduces the rank of the array.

This rule will help you predict the dimensions of the result of a reduction.

Rule

When reduce is applied along the nth dimension of an array, the shape of the result is the same shape of the array, but without its nth item.

The rank of the result is 1 less than the rank of the original array.

Whenever you want to calculate the sum along a particular dimension of an array, think of the dimensions in terms of concrete things: years, lines, months, etc. This should help you.

4.9.3. Special Notations#

By default, if no axis is specified, the reduction is applied along the last dimension of the array.

This means +/prod and +/[3]prod are the same,

(+/prod)(+/[3]prod)
1

much like +/forecast and +/[2]forecast are the same:

(+/forecast)(+/[2]forecast)
1

But it is also common to work along the first dimension of an array. For this reason, APL includes a special symbol for reduction along the first dimension: (you can type it with APL+/).

This means +⌿prod and +/[1]prod are the same,

(+prod)(+/[1]prod)
1

much like +⌿forecast and +/[1]forecast are the same:

(+forecast)(+/[1]forecast)
1

Note

If one specifies an axis after the symbol / or , the function is applied along the specified axis, regardless of the symbol that is actually used.

We demonstrate this with two examples:

(+[3]prod)(+/[3]prod)
1
(+[1]forecast)(+/[1]forecast)
1

4.10. Our First Program#

The expression we wrote in Section 4.8.7 to calculate the average of a set of values is one that we may want to use time and time again. So let us store it as a program, or, to use the proper APL terminology, as a user-defined function.

There are many different ways to define functions, and these will be covered in detail in the “User-Defined Functions” chapter. For now we shall use the simplest, which is perfectly suitable for straightforward calculation functions like this one. Let’s type:

Average  {(+/)÷()}
  • Average is the name of the function. It is followed by the definition, delimited by a pair of curly braces { and };

  • is a generic symbol that represents the array that will be passed as the right argument of the function; and

  • is a generic symbol that represents the array that will be passed as the left argument of the function, if any.

The symbols and are obtained using APL+w and APL+a, respectively.

For more complex multi-line functions it is obviously more appropriate to use a text editor. However, this is beyond the scope of this chapter.

Once defined, this function may be invoked directly, just as if it were a built-in (primitive) function:

Average salaries
2415.69
Average 12 74 56 23
41.25

The word Average can now be used in any APL expression. We have enriched the vocabulary which can be used to process data in this workspace (provided that we save it).

Be patient: we shall see many other possibilities in the “User-Defined Functions” chapter.

4.11. Catenate#

Catenate is a dyadic function which joins two arrays together and it is represented by the comma (,). The function is sometimes referred to as concatenate, and people with programming experience in other programming languages generally recognise that name better.

4.11.1. Catenating Vectors#

Catenate is easy to understand:

X  24 15 67 89
Y  11 33 75
X,Y
24 15 67 89 11 33 75

It is like joining two sentences together, so it is easy to remember which symbol to use. You can see that the length of the result (≢(X,Y)) is equal to the sum of the lengths of the arguments ((≢X) + (≢Y)):

((X)+(Y))=≢X,Y
1

Character strings are processed in the same way:

C  'Tell me'
D  'More'
C,D
Tell meMore

Note that there is no space inserted between the contents of the two vectors, just like there was no number inserted between X and Y when we catenated them above. When we catenate a vector of 7 characters (like C) with a vector of 4 characters (like D), the result must have 11 characters.

When you catenate an empty vector to another vector, the result is the same as the original. Let us define an empty numeric vector v:

v  00

(We could have used instead.)

Notice how the numeric vector X remains unchanged when catenated with v:

X,v
24 15 67 89

Similarly, catenating character strings with '' does nothing:

C,'',D
Tell meMore

4.11.2. Catenating Other Arrays#

It is possible to catenate two arrays if their shapes are compatible. The axis along which the catenation is to be performed must be specified, if it is different from the default.

Let us use three matrices A, B and C:

A  3 4  'A'
AAAA AAAA AAAA
B  2 4  'B'
BBBB BBBB
C  3 3  'C'
CCC CCC CCC

The possible catenations are:

  • the vertical catenation of A and B; and

_images/Vertical_Catenation_AB.png

Fig. 4.4 Matrices A and B catenated together vertically.#

  • the horizontal catenation of A and C.

_images/Horizontal_Catenation_AC.png

Fig. 4.5 Matrices A and C catenated together horizontally.#

It is not possible to catenate B and C because none of their dimensions are compatible.

To catenate vertically, we want the two matrices to be stacked on top of each other, creating a matrix with more rows, i.e. we want to create a matrix with a larger 1st dimension:

A,[1]B
AAAA AAAA AAAA BBBB BBBB

The shape of the result is

A,[1]B
5 4

and writing

B,[1]A
BBBB BBBB AAAA AAAA AAAA

puts B on top of A instead.

Similarly, to stack matrices A and C horizontally we create a matrix where the 2nd dimension is larger:

A,[2]C
AAAACCC AAAACCC AAAACCC

The shape of the result is

A,[2]C
3 7

and writing

C,[2]A
CCCAAAA CCCAAAA CCCAAAA

puts C to the left of A instead.

In the same way as for reduce, the axis operator indicates which dimension will change during the operation, as we can see by inspecting the shapes of A, B, C and the stacked matrices:

A
3 4
B
2 4
A,[1]B
5 4

Using ,[1]changes the 1st dimension. Using ,[2] instead, we change the 2nd dimension:

A
3 4
C
3 3
A,[2]C
3 7

The following nested array shows the four possible different catenations side by side:

(A,[1]B) (B,[1]A) (A,[2]C) (C,[2]A)
┌────┬────┬───────┬───────┐ │AAAA│BBBB│AAAACCC│CCCAAAA│ │AAAA│BBBB│AAAACCC│CCCAAAA│ │AAAA│AAAA│AAAACCC│CCCAAAA│ │BBBB│AAAA│ │ │ │BBBB│AAAA│ │ │ └────┴────┴───────┴───────┘

Rule

It is possible to catenate two arrays A and B along a given dimension provided that they have the same rank, and provided that all other dimensions have the same lengths.

The operation is written as A,[i]B if we wish to catenate along the ith dimension.

It is also possible to catenate an array A of rank \(n\) to another array B of rank \(n-1\). The catenation must then be done along a dimension of A such that its other dimensions are strictly identical to those of B.

For example, it is possible to concatenate a vector to a matrix, provided that the vector has the same length as the corresponding dimension of the matrix:

A,[1]'JUMP'
AAAA AAAA AAAA JUMP
A,[2]'TOP'
AAAAT AAAAO AAAAP

In the first example, 'JUMP' has length 4 and the shape of A is 3 4, so we can see 'JUMP' as having shape 1 4 and so we must catenate along [1]. The shape of the result is

A,[1]'JUMP'
4 4

and we see it was the 1st dimension that changed.

In the second example, 'TOP' has length 3 and the shape of A is 3 4, so we can see 'TOP' as having shape 3 1 and so we must catenate along [2]. The shape of the result is

A,[2]'TOP'
3 5

and we see it was the 2nd dimension that changed.

Example

We can add a row of totals to the bottom of a matrix Y with an expression like Y,[1] (+/[1]Y):

forecast,[1] (+/[1]forecast)
90 160 420 500 20 30 110 450 170 370 290 360 340 190 320 120 510 370 150 460 240 520 490 280 690 1260 1150 1510 1310 1040

The parentheses are for ease of interpretation; they are not necessary.

In a similar way, it is possible to concatenate a matrix to a 3D array.

Example

We would like to append to prod (a 3D array) the production of a subcontractor, organised as an array of 5 years and 12 months.

⎕RL  73
subcon  ?5 1220
9 16 10 18 2 3 11 13 17 5 4 2 19 12 19 5 15 14 20 17 4 9 5 13 4 16 7 2 14 20 15 17 18 13 6 19 10 6 20 13 5 19 5 10 17 16 19 20 18 14 15 15 3 7 9 20 6 8 8 17

The shape of subcon is

subcon
5 12

and the shape of prod is

prod
5 2 12

The two must be catenated along the 2nd dimension of prod and the result will have the shape

prod,[2]subcon
5 3 12

You see, it is as if subcon had the length 1 along the catenation dimension (the missing one), i.e. it as if subcon had shape 5 1 12.

prod,[2]subcon
9 16 42 50 2 3 11 45 17 37 29 36 34 19 32 12 37 15 46 24 49 28 36 9 9 16 10 18 2 3 11 13 17 5 4 2 29 5 45 23 27 4 23 16 39 22 22 2 46 20 47 17 18 25 13 38 42 38 45 28 19 12 19 5 15 14 20 17 4 9 5 13 37 19 5 10 30 49 16 18 46 47 47 3 39 23 41 20 6 40 21 22 40 49 20 1 4 16 7 2 14 20 15 17 18 13 6 19 13 36 12 40 12 15 24 20 21 12 19 25 8 48 22 37 33 1 39 30 50 50 8 35 10 6 20 13 5 19 5 10 17 16 19 20 38 31 24 40 23 7 20 34 35 6 19 27 41 1 14 50 17 42 6 35 12 48 30 29 18 14 15 15 3 7 9 20 6 8 8 17

4.11.3. Catenating Scalars#

When a scalar is catenated to an array it is repeated as many times as necessary to match the length of the appropriate dimension of the array.

Here are two examples, using the matrix A from before:

A,[1]'-'
AAAA AAAA AAAA ----
A,[2]'*'
AAAA* AAAA* AAAA*

This property is very useful, because it saves us working out how many items are needed to match the corresponding dimension of the array.

We can also catenate two scalars. The result is of course a 2-item vector:

7,9
7 9

4.11.4. Special Cases and Notations#

By default, if no axis is specified, catenation works along the last dimension of the array(s).

So

A,C
AAAACCC AAAACCC AAAACCC

is equivalent to

A,[2]C
AAAACCC AAAACCC AAAACCC
(A,C)(A,[2]C)
1

APL also includes a special symbol that means “catenate along the first dimension”; this symbol is a comma topped by a minus sign: . It can be obtained by APL+Shift+,.

So

AB
AAAA AAAA AAAA BBBB BBBB

is equivalent to

A,[1]B
AAAA AAAA AAAA BBBB BBBB
(AB)(A,[1]B)
1

Note

If an axis is specified, the operation is processed according to the axis specification, regardless of the symbol (, or ) that is actually used.

This means A,[2]C and A⍪[2]C are both equivalent to A,C and A,[1]B and A⍪[1]B are both equivalent to A⍪B.

4.11.5. Dyalog Idioms and Idiomatic Phrases#

It is common to see catenate used in conjunction with reduce, as ,/. Let us start by understanding what it does:

0 (1 2) (3 4 5) (6 7 8 9)
┌─┬───┬─────┬───────┐ │0│1 2│3 4 5│6 7 8 9│ └─┴───┴─────┴───────┘
,/0 (1 2) (3 4 5) (6 7 8 9)
┌───────────────────┐ │0 1 2 3 4 5 6 7 8 9│ └───────────────────┘
'a' 'bcd' 'efghi'
┌─┬───┬─────┐ │a│bcd│efghi│ └─┴───┴─────┘
,/ 'a' 'bcd' 'efghi'
┌─────────┐ │abcdefghi│ └─────────┘

We can see above that reduce by catenate joins all items of a vector together. Using ,/ is so common that you will get used to looking at ,/ and reading it as join, instead of reduce by catenate. Like ,/, there are many expressions which can be understood as an entity at first sight (with some practice!).

For someone who knows nothing of APL the expression above may take a bit of time to digest (even if they have an extensive knowledge of other programming languages), and they cannot readily appreciate that an APL programmer can understand it immediately, without having to read each of the symbols one by one.

This is not a paradox. For young children, reading the word “Daddy” is complex. It requires the comprehension of a sequence of letters one-by-one. I presume that you no longer do that, do you? You do not read the letters; you understand the word as a whole. This is exactly the same for the above idiomatic phrase.

These common combinations of primitives are usually termed as idioms or idiomatic phrases. We prefer the latter denomination to avoid any confusion with Dyalog APL’s idiom recognition. APLcart is a very comprehensive online collection of such idiomatic phrases, although not all entries in APLcart are idiomatic phrases. It is also noteworthy that there is no objective criteria to determine if a combination of primitives is an idiomatic phrase or not: it depends on your skill, experience, and even the context you are working on.

Some idiomatic phrases are so common and useful that Dyalog APL introduced a special idiom recognition feature that speeds up the processing of APL code for many popular phrases. Both RIDE and the Windows IDE even have the capability of colouring these differently. For Dyalog APL, an idiom is like a “shortcut primitive” that replaces a sequence of consecutive primitives. This replacement will allow for a speed up of the code in which it shows up.

For example, we have been using a short idiom fairly often: ≢⍴ (or ⍴⍴ in Ancient times) computes the rank of an array. Because ≢⍴ is recognised by Dyalog APL’s idiom recognition feature, the interpreter doesn’t need to process two instructions:

  1. find shape of right argument;

  2. return tally of elements of right argument.

Instead, it processes a single instruction:

  1. find rank of right argument,

which can be computed by a faster method than the 2 separate steps above.

4.12. Replicate#

4.12.1. Basic Approach: Compress#

To extract scattered values from a vector, we can use indexing:

contents[5 6 11]
85 96 82

We can also use a new function named compress. It takes a Boolean vector as its left argument, and any array of appropriate shape as its right argument. The items of the right argument which match the 1s in the left argument are preserved, whereas those which match the 0s are removed. It acts like a mask or a filter:

0 1 1 0 / 42 15 79 66
15 79
1 0 1 0 0 0 0 1 1 / 'Drumstick'
Duck

This is extremely useful, because we can use compress to select items which match a given condition.

For example, let us extract from contents the values which are greater than 80.

The Boolean vector for the left argument is obtained by contents>80, and the selection is made by:

(contents>80) / contents
85 96 82

Of course, the same operation can be applied to any array of any dimension. For higher dimensional arrays, we can use the axis to specify along which axis to compress. For example, if we have a matrix of chemical formulas:

chemistry  3 5'H2SO4CaCO3Fe2O3'
H2SO4 CaCO3 Fe2O3
1 0 1 /[1] chemistry
H2SO4 Fe2O3

By using /[1] we are compressing the 1st dimension of chemistry, hence selecting two rows, corresponding to the two 1s in the vector on the left.

1 1 0 1 0 /[2] chemistry
H2O CaO FeO

If we use /[2] we are compressing the 2nd dimension of chemistry, hence selecting three columns. In this example columns 3 and 5 have been removed.

Compress is an excellent tool which allows you to:

  • extract some useful items from a variable; or

  • remove some unwanted items from a variable, which is the same thing.

Tip

Every time you obtain a Boolean vector, you should immediately think of two major things you can do with it: count or select.

For example, using contents, we can produce a Boolean vector that shows which items are smaller than 50:

bin  contents < 50

Then, we can:

  • count the items that are smaller than 50:

+/ bin
5
  • select (or extract) said items:

bin / contents
12 30 22 44 27

Hint

Programmers who are new to APL and who are familiar with indexing as the natural selection mechanism may be tempted to use the Boolean selection vector to create some indices, and then use the indices to select the desired items.

This works very well, for example:

ix  bin / ⍳≢contents
contents[ix]
12 30 22 44 27

However, this is an unnecessary complication that wastes memory and processing time, compared to the straightforward selection shown above.

4.12.2. General Case: Replicate#

In fact, compress is just a special case of a more comprehensive function named replicate. Its left argument can be any vector of integer values, each of which produces the following result:

Signum of the left item

Effect on the corresponding right item

1

item is replicated the number of times specified by the left item

0

item is suppressed

¯1

item is replaced by as many fill items as is indicated by the left item

The concept of a fill item is new and will be discussed in full in the “Nested Arrays” chapter. For now, you only need to know that the fill item for a simple numeric array is 0 and the fill item for a simple character array is a blank space.

Here are some examples, using the same left argument applied to numeric and character vectors:

0 1 3 0 / 42 15 79 66
15 79 79 79

42 and 66 have been removed, as their corresponding left items were 0. 15 was replicated 1 time and 79 was replicated 3 times, as indicated by their corresponding left items.

0 1 3 0 / 'boat'
oaaa
2 ¯3 1 0 / 42 15 79 66
42 42 0 0 0 79

For the example above, 15 has been replaced by 3 zeroes because the fill item for simple numeric arrays is a zero.

2 ¯3 1 0 / 'boat'
bb a

For this example, the letter “o” was replaced by 3 blank spaces because the fill item for simple numeric arrays is ” “.

4.12.3. Scalar Left Argument#

If the left argument of compress or replicate is a scalar, it applies to all the items of the right argument.

When the left argument is 1, all the items are retained:

v  'Phew'
1/v
Phew

When the left argument is a positive integer, all items are replicated that number of times:

3/v
PPPhhheeewww

When the left argument is 0, no item is replicated and we get an empty vector:

0/v
''0/v
1

As you can see above, when we use 0 as the left argument to replicate we get an empty vector. Because v was a simple character vector, we get an empty character vector.

Finally, when the left argument is a negative integer, all items are replaced by the fill item as many times as the magnitude of the left argument indicates:

¯3/v

Visually, we can’t distinguish this case from 0/v because blank spaces aren’t visible, but there are 3×4 of them:

¯3/v
12

Replacing v with a simple numeric vector should help clear any doubts about what is happening:

¯3/1 2 3 4
0 0 0 0 0 0 0 0 0 0 0 0

4.12.4. Replicate with Axis#

Like reduce and catenate, replicate works along the last dimension of an array by default. However, it is possible for it to work on any dimension by using the axis. It is also possible to use , which we have already seen, to work on the first dimension by default.

For example,

0 1 0  chemistry
CaCO3

which is equivalent to

0 1 0 /[1] chemistry
CaCO3

Beware, the result obtained this way is not a vector, but a matrix having only one row:

0 1 0chemistry
1 5

You must not confuse reduce and replicate: even if the symbol used is the same, they are completely different operations:

  • reduce takes a function as its left operand; it is a monadic operator (e.g. +/ contents); whereas

  • replicate takes a vector as its left argument and an array as its right argument; it is a dyadic function (e.g. vec/ contents).

4.13. Position (Index Of)#

4.13.1. Discovery#

It is very often necessary to locate the positions of particular values in a list of items. To solve this, APL has a special function named index of, represented by the Greek letter iota (). This symbol can be obtained by Ctrl+i (the initial letter of iota). Let us see how it works:

vec  15 42 53 19 46 53 82 17 14 53 24
vec  19 14 53 49 15
4 9 3 12 1

Above we asked for the positions of five values (19, 14, 53, 49 and 15) and naturally we obtain five answers:

  • the result tells us that 19, 14 and 15 appear in positions 4, 9 and 1 respectively;

  • the result also tells us that 53 appears in position 3. This is of course true, but it also appears in positions 6 and 10, which are not included in the result. This is a necessary restriction: if we had searched for five values and obtained seven results, it would not have been possible to say where each value appears. This is the reason why index of returns only the first occurrence of each value; and

(We shall see later that this is an advantage: if instead we need to find all the positions in which a value occurs, there is another function that we can use - see Section 4.14.1 below).

  • surprisingly, the result tells us that 49 appears in position 12, though vec has only 11 items! This is the way that index of indicates a missing value. We shall see that it is a great advantage, too.

The following rule explains how dyadic Iota works when the left argument is a vector:

Rule

In the expression r haystack needles we look for the needles in the haystack, and

  • haystack can be a vector of any type: numeric, character, mixed, nested;

  • needles can be any array (any type, any shape, any rank);

  • r will have the same rank and shape as needles;

  • the items of r contain the positions of the first occurrence of the corresponding items of needles in haystack;

  • items which do not appear in haystack give the result 1+≢haystack.

'ABC'  57
4

A number cannot occur in a 3-item character vector, so the result is 4.

4 8  '4 8'
3 3 3

Similarly, characters cannot appear in a 2-item numeric vector, so each character results in a 3.

alpha  'ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789'
alpha  chemistry
8 30 19 15 32 3 38 3 15 31 6 38 30 15 31

The two lower case letters in chemistry give the answer 38 because alpha has 37 items. Also notice how the shape of chemistry and alpha chemistry is the same, like the rule above specified.

(chemistry)(alphachemistry)
1

We can also use nested vectors:

'Tee' (3 7) 'Golf'  3 7 (3 7) 'Tee' 'Green'
4 4 2 1 4

The function index of is one of the most important primitive functions in APL. It is very flexible and it can be used in many situations, as shown in the following examples.

Warning

In the expression A⍳B we search for B in A whereas in A∊B we search for A in B. Do not be confused!

4.13.2. Application 3#

A car manufacturer decided that they will offer their customers a discount on the catalogue price. The country has been split into 100 geographical areas, and the discount rate will depend on the geographic area according to the following table:

Area

Discount

17

9%

50

8%

59

6%

84

5%

89

4%

Others

2%

which we save to two vectors:

area  17 50 59 84 89
discount  9 8 6 5 4 2

The first task is to calculate the discount rate to be claimed for a potential customer who lives in area d; for example

d  84

Let us see if 84 is in the list of favoured areas:

area  d
4

We can see that 84 is the 4th item in the list.

Let us find the current discount rate for this index position:

discount[4]
5

This customer can claim a 5% discount.

We could simply write

discount[aread]
5

Now, what if a customer lives in any other area, such as 75, 45 or 93?

The expression area⍳d will return the result 6 for all these area codes, because these values are absent from area. Then, discount[6] will always find the rate 2%, as specified. Here we can see that it is an advantage that index of returns 1 + the number of items in the vector to be searched.

A Vector Solution

The importance of this approach to finding the discount rates is that it is vector-based. If publicity attracts crowds and therefore d is no longer a scalar but a vector, the solution is still valid.

As an example, consider the following vector of area codes and the respective discount rates:

d  24 75 89 60 92 50 51 50 84 66 17 89
discount[aread]
2 2 4 2 2 8 2 8 5 2 9 4

We have achieved all this without a function, neither a “loop” nor a “test”. And it works for any number of areas. Readers who know other programming languages will probably appreciate the simplicity of this approach.

4.13.2.1. Changing The Frame of Reference#

In reality, the expression that we have just written is an example of an algorithm for “changing the frame of reference”. Don’t panic, this term may seem esoteric, but the concept is simple: a list of area numbers (the initial set) is translated into a list of discount rates (the final set). The algorithm comprises only the function index of and indexing:

Algorithm

r finalSet[initialSet values]

Let us imagine the initial set to be an alphabet composed of both lowercase and uppercase letters, and the final set to be composed of only uppercase letters, with a blank space in the middle:

alphLower  'abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ'
alphUpper  'ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ*'

Now, let us write a sentence; we will write it in French in order to show what happens with missing characters:

tale  'Le Petit Chaperon-Rouge a bouffé le Loup'

If we apply the algorithm seen above, the expression will convert the text from lower to upper case:

alphUpper[alphLowertale]
LE PETIT CHAPERON*ROUGE A BOUFF* LE LOUP

As one might expect, the characters '-' and 'é', which are absent from the initial alphabetic set, have been replaced by the '*', the “extra” character at the end of the final set. This works because once again the final set is one item longer than the initial set.

Once more, the logical steps needed to solve the problem are easily translated into a programming solution, and the programmer can thereby direct all his attention to solving the problem.

4.13.3. General Case#

The left argument to index of need not be a vector. In fact, in the expression haystack needles, haystack can be an array of any rank, type and shape, as long as it is not a scalar. However, needles must have a shape that is appropriate for the haystack you use.

We will see what this means by using a 3D haystack as an example:

haystack  2 3 41 2 3 4 5 6 7 8 9 0
1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4

When index of is used, the first thing Dyalog APL does is look at its left argument and try to figure out what is the shape of the things contained in the haystack. For that matter, we first inspect the shape of the haystack

haystack
2 3 4

and interpret it as

haystack contains 2 items of shape 3 4

which means we can look for needles with shape 3 4 inside the haystack. Therefore, for needles to be seen as “containing items of shape 3 4” its shape must also end in 3 4. Whatever precedes the final 3 4 in the shape of needles is what dictates the shape of the final result.

If the shape of needles is too short for it to match the shape of the items the haystack contains, we get a RANK ERROR because needles doesn’t have enough dimensions.

Similarly, if the shape of needles is long enough but its trailing dimensions don’t match those of the left argument, we get a LENGTH ERROR because the things we are comparing have different lengths along their dimensions.

In the table below we give examples of some haystack and needles shapes, along with the shape of the items that thinks haystack contains and the shape of the result r haystack needles (remember that is the shape of a scalar):

⍴haystack

⍴needles must end with

⍴needles

⍴r

2 3 4

3 4

3 4

3 1 3 4

3 1

3 1 4 4

LENGTH ERROR

1 2 3 3 4

1 2 3

3

RANK ERROR

6 2

2

4 2

4

2

3

LENGTH ERROR

5

1 2 3

1 2 3

1 3

1 3

5

5

As a practical example, if we take the haystack we defined above, we can look for 3 by 4 matrices in there. Our needles variable has 4 such matrices:

needles  4 3 41 2 3 4 5 6 7 8 9 0
1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8
haystack  needles
1 2 3 3

This shows that the first and second matrices were found in positions 1 and 2, respectively, and the third and fourth matrices were found nowhere, thus getting 3 as a result. The result is 3 because 2 is the number of items that haystack has.

Now we write the rule that dictates how index of works in the general case:

Rule

In the expression r haystack needles we look for the needles in the haystack, and

  • haystack can be any array of rank hr with hr being at least 1;

  • needles can be any array of rank nr with nr being at least hr-1;

  • the last hr-1 numbers of the shape of needles and the last hr-1 numbers of the shape of haystack must be the same;

  • r has rank equal to nr-hr-1 and shape equal to the first nr-hr-1 numbers of the shape of needles;

  • the items of r contain the positions of the first occurrence of the corresponding items of needles in haystack;

  • items which do not appear in haystack give the result 1+m if m is the leading number in the shape of haystack, i.e. m ≢haystack.

4.14. Where#

The primitive function where is the monadic use of the iota underbar , which you can type with APL+Shift+I. The simplest use case for this primitive is to give it a simple Boolean array, for which finds where the values 1 are.

For example, given the contents vector:

contents
12 56 78 74 85 96 30 22 44 66 82 27

What items are greater than 75?

contents > 75
0 0 1 0 1 1 0 0 0 0 1 0

And where are they?

contents > 75
3 5 6 11

They are at positions 3, 5, 6 and 11, referring respectively to the elements 78, 85, 96 and 82.

4.14.1. Application 4#

You probably remember that the function index of returns only the first occurrence of a value in a vector (cf. Section 4.13.1). Using where we can find all the occurrences.

Here is a vector, in which we would like to find the positions of the number 19:

vec  41 17 19 53 42 27 19 88 14 56 19 33
vec=19
0 0 1 0 0 0 1 0 0 0 1 0

Now that we have a Boolean vector, we can just use to find out where the 1s are:

vec=19
3 7 11

It is as simple as that!

Of course the same search technique will work on characters, because what we really care about is the Boolean vector we generate at a later step, not what the initial vector was. For example, let us find all the letters “a” in a sentence:

sentence  'Panama is a canal between Atlantic and Pacific'
sentence='a'
2 4 6 11 14 16 30 36 41

Having found all the “a”s, we may wish to find all the lowercase vowels. For that matter, we need to create a Boolean vector with 1s in the positions with lowercase vowels. In the example above, sentence='a' worked because we are allowed to compare a vector with a single scalar, but now we can’t change this to

sentence='aeiouy'        ⍝ 'y' is a vowel in many European languages
LENGTH ERROR: Mismatched left and right argument shapes
      sentence='aeiouy'        ⍝ 'y' is a vowel in many European languages
              ∧

The code above does not work because = is trying to compare the two vectors item by item, only to realise the vector 'aeiouy' is too short for that. Instead, what we can do is check whether or not each character of sentence is a member of the character vector 'aeiouy':

sentence'aeiouy'
0 1 0 1 0 1 0 1 0 0 1 0 0 1 0 1 0 0 0 1 0 0 1 1 0 0 0 0 0 1 0 0 1 0 0 1 0 0 0 0 1 0 1 0 1 0

Having computed this Boolean vector, the finishing touch is to compute where the vowels were found:

sentence'aeiouy'
2 4 6 8 11 14 16 20 23 24 30 33 36 41 43 45

4.14.2. Increasing The Dimension#

Up until now we only used simple vectors as arguments to where, but in the beginning we talked about Boolean arrays, not vectors.

For higher dimensional arrays where behaves the same way: it returns the indices of the positions that contain 1s. Similar to what we did above, we might want to find all the lowercase vowels in the monMat matrix:

monMat
January February March April May June
monMat'aeiouy'
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │1 2│1 4│1 5│1 7│2 2│2 5│2 6│2 8│3 2│4 4│5 2│5 3│6 2│6 4│ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘

The last item in the result above is 6 4, which means that row 6, column 4 of monMat contains a lowercase vowel: the final “e” in “June”. We can verify this with the function index you already learned about (cf. Section 3.5.6):

6monMat
June
6 4monMat
e

The example above also shows an interesting property of where: it always returns a vector, regardless of the shape of the input, and each item of the resulting vector is a suitable index to .

4.14.3. Simple, Not Nested#

Where does require that its argument be a simple array. If you provide a nested array, won’t know what to do with it. This is because there is no way of indexing into a nested array with an index vector like 3 or 6 4, or even something longer.

In fact, if the argument to where is a nested array, you get a DOMAIN ERROR:

(0 1)(1 1)
DOMAIN ERROR: Numeric value required
      ⍸(0 1)(1 1)
      ∧

4.14.4. Beyond Boolean Arrays#

Now that you have seen the most iconic use of where, you will be shown the full specification for it, as we have only used Boolean arguments so far.

Rule

In the expression R ⍸Y:

  • Y must be a simple Boolean vector or a simple numeric array with non-negative integers;

  • R is a vector, regardless of the shape of Y;

  • each non-negative element of Y has its index repeated in R as many times as the element’s value;

  • if Y only contains zeroes, R is an empty vector;

  • each element of R can be used as i in i⌷Y to retrieve an element from Y.

This repetition is similar to the way compress works when the left argument only contains non-negative integers:

3 0 0 2 0 1 / 'words!'
wwwdd!

The left argument to compress tells it to use the 1st element 3 times, to use the 4th element 2 times and to use the 6th element 1 time.

The same argument to returns the indices repeated as many times as they are needed:

3 0 0 2 0 1
1 1 1 4 4 6

In this case, we could even achieve the same effect as the compress expression above by using [] indexing:

'words!'[3 0 0 2 0 1]
wwwdd!

This breaks down if we want to include negative integers in the left argument to compress, which where doesn’t support.

In fact, if the argument array contains negative integers or other numbers which aren’t integers, DOMAIN ERRORs are issued:

0 0 1 0 ¯1
DOMAIN ERROR: Where right argument must be non-negative
      ⍸0 0 1 0 ¯1
      ∧
0 0 1 0 0.3
DOMAIN ERROR: Where right argument must be integer
      ⍸0 0 1 0 0.3
      ∧

4.14.5. Comparison of Membership, Index Of and Where#

We have discovered two different techniques, using the primitive functions membership and where, that allow us to look up one set of values in another and to determine the positions of the items of one set in the other. Depending on the problem that we have to solve, we can choose which of the two methods will be most appropriate for the job in hand. Consider the following example:

4.14.5.1. Example#

Imagine a company has subsidiaries in a number of countries; each country being identified by a numeric code. The country names are stored in a matrix named countries, and the country codes are stored in a vector named codes. To make things easier to read, let us show those two variables:

countries  9 13'France       Great BritainItaly        United StatesBelgium      Swiss        Sweden       Canada       Egypt        '
France Great Britain Italy United States Belgium Swiss Sweden Canada Egypt
codes  50 43 12 83 64 34 66 81 37
50 43 12 83 64 34 66 81 37

Now let us show those two variables side by side:

countries, codes
France 50 Great Britain 43 Italy 12 United States 83 Belgium 64 Swiss 34 Sweden 66 Canada 81 Egypt 37

So, Sweden is identified by 66 and Belgium is identified by 64.

All the sales made during the last month have been recorded in two vectors:

  • bhcodes identifies in which country each sale has been made, and

  • bhamounts identifies the amount of each sale.

Here are the two vectors:

bhcodes  83 66 12 83 43 66 50 81 12 83 12 66
83 66 12 83 43 66 50 81 12 83 12 66
bhamounts  609 727 458 469 463 219 431 602 519 317 663 631
609 727 458 469 463 219 431 602 519 317 663 631

Some countries have not sold anything (Belgium, for example) whereas other countries made several sales (Italy, for example).

4.14.5.2. First Question#

We would like to focus on some selected countries (43, 50, 37 and 66) and calculate the total amount of their sales. Let’s first identify which items of bhcodes are relevant:

selected  37 43 50 66
bhcodes  selected
0 1 0 0 1 1 1 0 0 0 0 1

Then we can apply this filter to the amounts, and add them up:

(bhcodes  selected) / bhamounts
727 463 219 431 631
+/ (bhcodes  selected) / bhamounts
2471

An alternative solution is to find the positions of the selected countries, then use this set of indices to get the amounts, and add them. The result is, of course, the same:

positions  bhcodes  selected
+/bhamounts[positions]
2471

As mentioned previously in Section 4.12.1, it is kind of a detour to solve this task using indexing, but here it serves to illustrate the different lookup methods.

Let us take a look at the selected countries and their positions in bhcodes:

selected
37 43 50 66
positions
2 5 6 7 12

Using membership, we have obtained positions which contains 5 items for the 4 countries in selected. This tells us that:

  • positions contains the indices of all of the occurrences of the selected countries in the list of sales;

  • however, the items in positions do not correspond to the items in selected on a one-to-one basis; we cannot say that country 43 is in position 2, or country 50 in position 5, and so on; and

  • it does not tell us that nothing was sold in country 37. Perhaps it would have been a good idea to identify this?

4.14.5.3. Second Question#

Now, let us suppose that we want to display the names of the selected countries. To do this, we must determine the positions of the selected country codes in the entire list of country codes, and get the corresponding names.

If we use the membership approach, here is what we get:

selected
37 43 50 66
positions  codes  selected
countries[positions;] , selected
France 37 Great Britain 43 Sweden 50 Egypt 66

(As a side note, above we catenated a character matrix with a numeric vector, so the result is a mixed matrix)

At first sight, this seems to be good: all the selected countries are displayed. However, they are not in the correct order: France is not 37, Sweden is not 50 and Egypt is not 66.

The problem with this method is the lack of a one-to-one correspondence between the selected countries and their positions in the list of sales. The positions will always be in the order that the countries appear in countries - because of . However, the Boolean vector codes selected is completely independent of the order of the items in selected: the expression returns the same result no matter how selected is ordered.

The correct method to use in order to solve this task is to use the function index of (dyadic iota):

positions  codes  selected
countries[positions;] , selected
Egypt 37 Great Britain 43 France 50 Sweden 66

It is the one-to-one relationship between the items of the right argument to index of (selected) and the items of its result (positions) that guarantees a correct result.

4.14.5.4. Comparison#

The following table summarises the most important properties of the two methods:

Method

Properties

pos ⍸list data

the items in pos do not have a 1-to-1 correspondence with the items in data

instead, the items in pos correspond to the items in list

pos gives all the positions of multiple occurrences of list in data

pos does not explicitly identify missing values

pos list data

the items in pos do have a 1-to-1 correspondence with the items in data

pos ignores multiple occurrences; just gives the first

pos identifies missing values

The choice of method depends on the kind of problem you want to solve.

4.15. Index Generator#

4.15.1. Basic Usage#

When used as a monadic function, the symbol iota generates a vector of the first N positive integers. It is called index generator.

9
1 2 3 4 5 6 7 8 9

If we have to extract the first 12 items of a vector contents, we can write:

contents[1 2 3 4 5 6 7 8 9 10 11 12]
12 56 78 74 85 96 30 22 44 66 82 27

It is, of course, much easier to write

contents[12]
12 56 78 74 85 96 30 22 44 66 82 27

The result can be combined with simple arithmetic operations. For example, suppose we need to produce the following list of 6 values: 115 122 129 136 143 150 (note the increments of 7). We can do this as follows:

6
1 2 3 4 5 6
(6)-1
0 1 2 3 4 5
7×(6)-1
0 7 14 21 28 35
115+7×(6)-1
115 122 129 136 143 150

More generally, any arithmetic series of integers can be produced by the following algorithm:

Algorithm

r origin + step × (⍳length) - 1

Special Case

If ⍳n gives a vector of length n, then ⍳0 should give a vector of length 0, right? A vector having length 0 is an empty vector. In particular, this will be a numeric empty vector.

Let us check:

0

Nothing gets displayed above, so it does look like the numeric empty vector , which we can confirm with match:

≡⍳0
1

This was useful in early versions of APL, before the introduction of the zilde symbol ().

The definition of ⍳n given above reflects only a limited part of what this function can do; you will find more information in Section 4.19 at the end of this chapter.

4.15.2. Application 5#

Sometimes, a programmer needs to remove duplicate items from a vector and there is a well-known idiomatic way to do this. The idiom applies equally to numeric, character and nested vectors. Let us begin with a numeric vector:

vec  12 89 57 46 12 50 36 47 83 46 27 12

The algorithm is based on the comparison of two vectors:

⍳≢vec
1 2 3 4 5 6 7 8 9 10 11 12

which gives the position of each item of vec, and

vecvec
1 2 3 4 1 6 7 8 9 4 11 1

which identifies the first index in vec in which each element of vec is found. This may be a bit more complex to understand, so take your time to digest it. We are using to identify the positions of the items of vec in vec itself. But because index of only returns the first occurrences, we get, for each item, the position where this value appears for the first time.

If we then compare these two vectors, we get a Boolean vector indicating which items of vec are their first occurrences and which are repetitions:

(⍳≢vec)=vecvec
1 1 1 1 0 1 1 1 1 0 1 0

Finally, we can use compress to keep only the first occurrences, so that the algorithm is as follows:

Algorithm

((⍳≢vector) = vector vector) / vector

((⍳≢vec)=vecvec)/vec
12 89 57 46 50 36 47 83 27

The result above has no duplicates.

It also works on character arrays and on nested arrays:

text  'All men are created equal'
((⍳≢text)=texttext)/text
Al menarctdqu
nested  'one' 1 (2 2⍴⍳4) 9 'nine' 'five' 'nine' 1 'two' (2 2⍴⍳4) 'two' 'one'
┌───┬─┬───┬─┬────┬────┬────┬─┬───┬───┬───┬───┐ │one│1│1 2│9│nine│five│nine│1│two│1 2│two│one│ │ │ │3 4│ │ │ │ │ │ │3 4│ │ │ └───┴─┴───┴─┴────┴────┴────┴─┴───┴───┴───┴───┘
((⍳≢nested)=nestednested)/nested
┌───┬─┬───┬─┬────┬────┬───┐ │one│1│1 2│9│nine│five│two│ │ │ │3 4│ │ │ │ │ └───┴─┴───┴─┴────┴────┴───┘

4.15.2.1. Unique#

Although still useful as an example, the idiom described above is now completely obsolete because there is a primitive function in APL that does exactly this.

The primitive, called unique, is represented by the symbol (APL+v) used as a monadic function:

 vec
12 89 57 46 50 36 47 83 27
 text
Al menarctdqu
 nested
┌───┬─┬───┬─┬────┬────┬───┐ │one│1│1 2│9│nine│five│two│ │ │ │3 4│ │ │ │ │ └───┴─┴───┴─┴────┴────┴───┘

The symbol is the mathematical symbol for set union and we will see this is not a coincidence.

4.15.3. Application 6#

Some applications of index generator are extremely basic, but so useful! Suppose that you invest a certain sum of money, €6,000 for example, and you expect an interest rate of 4% p.a.. How is the investment expected to grow in the next 5 years?

We will have to calculate 1.04 to the power of 0, 1, 2, 3 and 4. The index generator will help us:

6000×1.04*(6)-1
6000 6240 6489.6 6749.18 7019.15 7299.92

4.16. Ravel#

The function ravel is represented by the monadic use of comma (,). Applied to any array, it returns all its items as a vector.

Naturally, if the array is already a vector, ravel does not change anything.

Let us see how it works on some matrices.

⎕RL  73
tests  ?6 3100
73 16 42 50 2 67 11 81 37 93 100 98 83 32 76 51 37 15
,tests
73 16 42 50 2 67 11 81 37 93 100 98 83 32 76 51 37 15

The items of the matrix have been strung out and returned as a vector, row by row.

chemistry
H2SO4 CaCO3 Fe2O3
,chemistry
H2SO4CaCO3Fe2O3

A common use of ravel is to transform a scalar into a 1-item vector. The difference between a scalar and a 1-item vector is not readily obvious, until you use it as an index into a matrix.

Suppose that you need to select a particular set of columns cols from the matrix forecast. As long as cols contains more than one value, the result will be a matrix:

cols  1 4 6
forecast[;cols]
90 500 30 110 370 360 340 120 370 150 520 280

But if cols happens to have only a single value, which is a scalar, the result returned is a vector (we already mentioned this in Section 3.5.3):

cols  4
forecast[;cols]
500 370 120 520

The rank of the result may be critical if some other expression in your program expects a matrix.

To make certain that your indexing expression always returns a matrix, you must ensure that your index will always be a vector by using ravel:

forecast[;,cols]
500 370 120 520

With ,cols, whatever the rank of cols is, the result will always be a matrix.

Ravel can be associated with an axis specifier; this will be discussed in Section 4.19.5.

4.17. Empty Vectors and Black Holes#

When we apply a scalar dyadic function to a vector and a scalar, the rule is that the result has the same size as the vector:

42 75 86 31 + 10
52 85 96 41

Above, a scalar added to a 4-item vector gives a 4-item vector, too.

Below, a 7-item vector compared to a scalar gives a 7-item vector:

'MAMMOTH' = 'M'
1 0 1 1 0 0 0

But what happens if the vector is empty? The rule says that the result must have the same size as the vector: therefore it should be empty, too, regardless of the (scalar) function that we used!

hole  
hole + 3

Notice how nothing is displayed above: the result is empty.

The same thing happens if we multiply by 100, for example:

hole × 100
hole = 0
hole = hole
hole
0
hole  
1

If you were working on the terminal and getting all these empty results, you could compare hole to with match and conclude hole is an empty numeric vector.

Empty vectors look very much like black holes: they absorb everything (but only when used with scalar functions). This may lead to some unexpected results.

4.17.1. Not All Empty Vectors Are Created Equal#

Something that might throw off new APLers is the fact that there are infinitely many different empty vectors. The first two examples being and '':


''

They look the same when displayed as results, but they are not the same:

''
0

And this is just the tip of the iceberg. If you recall the rule for where (monadic ), we know that ⍸Y returns an empty vector if Y only contains 0s, but an empty vector of what?

If Y contains positive integers, then the result of ⍸Y will contain some indices that can be used to index into Y and the shape of those indices depends on the shape of Y:

 1 0 1 0
1 3
 2 21 0 1 0
┌───┬───┐ │1 1│2 1│ └───┴───┘

So, in the first example the result is a vector with 2 integers, and in the second example the result is a vector with 2 vectors, each containing 2 items.

If we change one of the 1s to a 0, here is what we will be getting:

 1 0 0 0
1
 2 21 0 0 0
┌───┐ │1 1│ └───┘

Now the first result is a vector with 1 integer and the second result is a vector with 1 vector of 2 items.

If we change the final 1 to a 0, we will be getting a vector with 0 integers (an empty numeric vector ) and a vector with 0 vectors of 2 items. For APL, these are not the same thing:

 0 0 0 0
≡⍸ 0 0 0 0
1
 2 20 0 0 0
≡⍸ 2 20 0 0 0        ⍝ different empty vectors
0

Tip

When you are dealing with empty vectors, you can use the incantation ⎕SE.Dyalog.Utils.repObj to get a more useful representation.

To illustrate this incantation, we will see what it returns for the empty vectors we saw above:

⎕SE.Dyalog.Utils.repObj ''
''
⎕SE.Dyalog.Utils.repObj 
⎕SE.Dyalog.Utils.repObj  0 0 0 0
⎕SE.Dyalog.Utils.repObj  2 20 0 0 0
0⍴⊂2⍴0

What the code cell above tells us is that 2 2⍴0 0 0 0 matches 0⍴⊂2⍴0, which you can check by copying & pasting the result above, or using APL+z to type :

(0⍴⊂20)( 2 20 0 0 0)
1

(You will learn about what does in a future chapter, don’t worry.)

_images/Hierarchy.png

4.18. Exercises#

Warning

The following exercises are designed to train you, not the computer.

For this reason, we suggest that you try to answer them on a sheet of paper, not on your computer. When you are sure of your answer, you can test it on the computer.

Exercise 4.1

Can you evaluate the following expressions?

3 × 2 + 6  3 × 2
12 6 27  11 + 3
4 5 6  4 + 2 5 9 > 1 6 8
7  25 6 17 - (2 × 3) + 9 3 5
((8 + 6) × 2 + 1) × 3 - 6 ÷ 3
(45) + 45
1  2  3  4  5  6  7
1  2  3  4  5  6  7
1 2 3  4 5 6

Exercise 4.2

Try to evaluate the following expressions. Be careful: they are not as simple as might first appear!

2 2+2 2
2+2 2+2
2+2,2+2
2,2+2,2

Exercise 4.3

Given the vector a 8 2 7 5, compare the results obtained from the following sets of expressions:

1+⍴a
a+1

and also

1+⍳⍴a
¯1+⍴a
⍳⍴a-1

Exercise 4.4

Using your knowledge of the order of evaluation in APL, re-write the following expressions without using parentheses.

((4)-1)3
7(9)3
1+((5)=1 4 3 2 5)×5

Exercise 4.5

Given a variable a, find an expression which returns the answer 1 if a is a scalar, and 0 if it is not.

Exercise 4.6

Given two scalars a and b, write an expressions which gives 7 if a is greater than or equal to b and 3 if a is smaller than b.

Exercise 4.7

Given two scalars a and b, find an expression which returns:

  • an empty vector, if a is zero, whatever the value of b;

  • 0, if b is zero, but a is not;

  • 3, if neither a nor b are zero.

Exercise 4.8

Unfortunately, your keyboard has been damaged and your and keys no longer work. Which other symbols could you use to replace them?

You can test your solutions on the vectors l and r:

l  0 0 1 1
r  0 1 0 1

Exercise 4.9

Given these three vectors:

g  1 1 1 0 0 1
m  0 0 1 1 0 1
d  1 0 1 0 1 0

Evaluate the following expressions:

gd
~gd
~g∨~d
d∧~g
gmd
(~d)(~g)
(mg)=(md)
(mg)(md)

Exercise 4.10

Evaluate the following expressions:

0 < 0  0 = 0  0 > 0
'sugar'  'salt'
11  '11'
'14'  '41'

Exercise 4.11

Write an expression to compute the number of times the letter “e” appears in a character vector. You can test it with this vector:

text  'The silence of the sea'

Exercise 4.12

How many expressions can you write to retrieve the last element of a vector? Can you write one that uses the operator reduce /?

You can test your expressions on these vectors:

vec  1 2 3 4 5 6
vec  (1 2) 3 (4 5) 6
vec  0 (50) (3 40) (1 2 30)

Exercise 4.13

We have conducted some experiments on a variable z:

      2  z
1 7
      + z
20
      z = 9
0
0
1
0

What is the value of z?

Exercise 4.14

We have conducted some experiments on a variable z:

      z = 0
0 1 0 0
1 0 0 1
      +/[2] z
20 6
      +/[1] z
8 7 6 5

What is the value of z?

Exercise 4.15

What are all the positions of the letter “e” in the character vector specified in exercise 11?

Exercise 4.16

Given a vector vec of any size and type (numeric or character or even nested), try to extract the items of vec which are in the odd positions (the 1st, the 3rd, the 5th, …).

You can test your solution on these vectors:

vec  1 2 3 4 5 6 7 8 9
vec  'CROANGGARYANTLULLBAPTWIZOSNSSB'
vec  (1 2) (2 3) (4 5 6) (6 9 42 1024)

Exercise 4.17

How many numbers are there in the variable prod used in this chapter? Try to answer with and without using ravel (monadic ,).

Exercise 4.18

How is it possible to remove all the values which do not fall between 20 (inclusive) and 30 (exclusive) from a given vector?

Exercise 4.19

Notice how these two expressions look the same:

      vec
4
      vec
4

Can you tell if/how they are different?

Exercise 4.20

Compute the depth () of these arrays:

42
42 41 40 39
2 242 41 40 39
1 (2 3) (4 5)
(1 2) (3 4 5) (6 7 8 9)
1 (2 (3 (4 (5 (6 7)))))
(2 24) (2 2⍴⍳47) (3 6⍴⍳18)

Exercise 4.21

In a vector, we would like to replace all the values that are smaller than 20 by 20, and replace all the values that are greater than 30 by 30. How can we do that? (In other words, how might we clip the values of a vector so that they are between 20 and 30, inclusive?)

Exercise 4.22

The following 6 expressions cannot be executed, but instead generate error messages; can you say why?

3+(5-(6+2)×4
121÷(4)-3
(¯X+5)*2
4 5 6+2 3-1
3-5
1 0 2 ¯1

Exercise 4.23

Write an APL expression which produces a vector of 17 numbers, the first being 23, with each subsequent number being equal to the preceding one plus 11.

Exercise 4.24

In a shop, each product is identified by a code. You are given a list of the codes and the corresponding prices:

pcodes  56 66 19 37 44 20 18 23 68 70 82
prices   9 27 10 15 12  5  8  9 98  7 22

A customer gives you a list of items they intend to buy as vector of code/quantity pairs: code, quantity, code, quantity, and so on.

wannaBuy  37 1 70 20 19 2 82 5 23 10

Can you evaluate their bill?

Note that this cannot be done easily in a single (and readable) APL expression, and you will therefore need to write several expressions.

The correct total should be 375.

Exercise 4.25

We have organised a lottery, and we have created five vectors:

⎕RL  73
tickets  ?1000999999
sold  tickets[(800+?200)?1000]
ours  sold[(?200)?≢sold]
winners  tickets[100?1000]
prizes  ?1001000

These are the meanings of these vectors:

  • tickets has the numbers of all the existing tickets;

  • sold has the numbers of the tickets which have been sold;

  • ours has the numbers of the tickets we bought ourselves;

  • winners has the numbers of the winning tickets, and

  • prizes has the prize amount associated with each respective number in winners vector.

Now, try to answer the following 4 questions:

  1. What are the numbers of the unsold tickets?

  2. Are there some winning tickets which have not been sold?

  3. How many winning tickets do we have?

  4. How much did we win?

Exercise 4.26

Can you calculate all the divisors of a positive integer number n?

Here are some results to check your answer:

n

Divisors

1

1

2

1 2

3

1 3

24

1 2 3 4 6 8 12 24

1337

1 7 191 1337

1234321

1 11 101 121 1111 10201 12221 112211 1234321

Solutions: the solutions can be found at the end of this chapter.

4.19. The Specialist’s Section#


Each chapter is followed by a "Specialist's Section" like this one. This section is dedicated to skilled APLers who wish to improve their knowledge.

You will find here rare or complex usages of the concepts presented in this chapter, or discover extended explanations which need the knowledge of some symbols that will be seen much further in the book.

If you are exploring APL for the first time, skip this section and go to the next chapter.

4.19.1. Division Control - ⎕DIV and Other Mathematical Subtleties#

4.19.1.1. Zero Divided by Zero#

In the beginning of this chapter we saw that 0÷0 returns 1:

0÷0
1

One might argue this is the case because any number divided by itself should give 1, like

3÷3
1
¯0.56÷¯0.56
1
1.2J0.6÷1.2J0.6
1

But 0÷0 is indeterminate from a mathematical standpoint, meaning 0÷0 by itself has no correct value. What follows from this is that carefully crafted contexts can make 0÷0 evaluate to whatever you please.

Let us explore that possibility briefly but without much mathematical rigour. Let us define an APL function that behaves like the mathematical function \(f(x) = 5x\), i.e. a function that takes a number and multiplies it by 5:

F  {5×}

And let us evaluate it at many different values:

x  ¯1+15÷30
¯0.933333 ¯0.866667 ¯0.8 ¯0.733333 ¯0.666667 ¯0.6 ¯0.533333 ¯0.466667 ¯0.4 ¯0.333333 ¯0.266667 ¯0.2 ¯0.133333 ¯0.0666667 0 0.0666667 0.133333 0.2 0.266667 0.333333 0.4 0.466667 0.533333 0.6 0.666667 0.733333 0.8 0.866667 0.933333 1
fx  F x
¯4.66667 ¯4.33333 ¯4 ¯3.66667 ¯3.33333 ¯3 ¯2.66667 ¯2.33333 ¯2 ¯1.66667 ¯1.33333 ¯1 ¯0.666667 ¯0.333333 0 0.333333 0.66666 7 1 1.33333 1.66667 2 2.33333 2.66667 3 3.33333 3.66667 4 4.33333 4.66667 5

And now let us study the ratio \(f(x)/x\) for those several values of x:

fx÷x
5 5 5 5 5 5 5 5 5 5 5 5 5 5 1 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5

If you look closely, the vector above has plenty of 5s and then a single 1 in the middle, corresponding to the 0÷0 division.

5fx÷x
0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

Let us try to create values of x that are tighter around 0 and perform the same division.

x  ¯0.1+100÷20
¯0.09 ¯0.08 ¯0.07 ¯0.06 ¯0.05 ¯0.04 ¯0.03 ¯0.02 ¯0.01 0 0.01 0.02 0.03 0.04 0.05 0.06 0.07 0.08 0.09 0.1
fx  F x
¯0.45 ¯0.4 ¯0.35 ¯0.3 ¯0.25 ¯0.2 ¯0.15 ¯0.1 ¯0.05 0 0.05 0.1 0.15 0.2 0.25 0.3 0.35 0.4 0.45 0.5
fx÷x
5 5 5 5 5 5 5 5 5 1 5 5 5 5 5 5 5 5 5 5

No matter how close to 0 we are, these ratios are always going to be returning 5. Because our function \(f(x)\) is well-behaved, in this context 0÷0 should return 5. Of course having to factor this in is an inconvenience for programming languages and most of them either

  • raise an error when one tries to divide zero by zero; or

  • return a previously-agreed-upon value (which is usually 0 or 1).

By default, Dyalog APL returns 1 when we compute 0÷0 as you saw above. This may sometimes be inappropriate. Suppose that we want to calculate the sales growth for 5 products, but the production of the 3rd product has not started yet, so its sales are currently zero:

before  20 31 0 120 63
after  22 27 0 149 59

The growth (in percent rounded) can be calculated like this:

0.5+100× (after-before)÷before
10 ¯13 100 24 ¯6

It is rather surprising to see that we’ve got a 100% growth on a product that does not exist!

To help circumvent this sort of issues, a so-called system variable named ⎕DIV is included in Dyalog APL to change the behaviour of division by zero.

By default, ⎕DIV is 0, but can also be set to 1.

⎕DIV

Behaviour

0

0÷0 gives 1

Any other number divided by 0 gives a DOMAIN ERROR

1

0÷0 gives 0

Any other number divided by 0 gives 0

To change the value of the system variable ⎕DIV you just assign to it as you would any other variable:

⎕DIV  1
0÷0
0
5÷0
0
⎕DIV  0
0÷0
1
5÷0
DOMAIN ERROR: Divide by zero
      5÷0
       ∧

Because ⎕DIV is a variable, it can be localised in the header of a function. Then the particular behaviour of division remains specific to that function and its sub functions.

Trying to change ⎕DIV to a value other than 0 or 1 also gives a DOMAIN ERROR:

⎕DIV  4
DOMAIN ERROR
      ⎕DIV←4
          ∧

4.19.1.2. Zero To The Power of Zero#

We also saw that 0*0 returns 1:

0*0
1

This is yet another convention that is in place just to make life easier for programmers, as 0*0 is indeterminate from a mathematical standpoint, similar to 0÷0.

But please, do not take my word for it! Notice that any number to the power of 0 gives 1:

¯3 4J5 1.2 (1) 1 2 3 * 0
1 1 1 1 1 1 1

Notice how 0 to the power of any positive number gives 0:

4J5 1.2 (1) 1 2 3 * 0
0 0 0 0 0 0

So how do we settle this for 0*0? If we look at it from the “a number to the power of 0” perspective, it should give 1. If we look at it from the “zero to the power of a number” perspective, it should give 0. So Dyalog APL just chose to go with the 1=0*0 convention, which is a fairly common one.

4.19.2. Derived Functions#

Let us sum the items of a vector:

+/ contents
672

In this expression, reduce takes plus as its operand.

This pair of symbols creates a new function, a so-called “derived function”. The derived function is monadic in this case, and it takes the variable contents as its argument.

contents is not the right argument of /, it is the argument of the derived function +/.

This is so true that +/ could be parenthesised:

(+/) contents
672

It can also be given a name:

sum  +/
sum 10 20 30
60

All operators create derived functions, i.e. functions that are derived from the function(s) the operators accept as operand(s).

4.19.3. Nor & Nand#

APL boolean algebra also includes two useful functions:

  • nor (stands for not-or), represented in APL by . You can type it with APL+Shift+9, which is “above” ;

  • nand (stands for not-and), represented in APL by . You can type it with APL+Shift+0, which is “above” .

The nor and nand functions give the negation of or and and respectively, and that is why their symbols are respectively and with a ~ on top.

This means that

  • a⍱b is equal to ~a∨b

  • a⍲b is equal to ~a∧b

as shown below:

a  0 0 1 1
b  0 1 0 1
2 4 (ab),(ab)
0 1 1 1 1 0 0 0
2 4 (ab),(ab)
0 0 0 1 1 1 1 0

Warning

Both and are associative, but and are not!

This means that (a∨b)∨c is equivalent to a∨(b∨c) and thus can be written a∨b∨c. Similarly for . but for nor and nand this is not the case:

a(ab)
0 1 0 0
(aa)b
0 0 1 0

Changing the order of evaluation changes the result, so is not associative. Same goes for .

Another consequence of this is that if bin has more than 2 items, ⍱/bin is not equivalent to ~∨/bin, and similarly for .

/b
1
~∨/b
0

This is probably the reason why those two primitives are rarely used in common business applications, although they are extremely useful in electronic automation, because they represent basic logical circuits.

4.19.4. Index Generator of Arrays#

We have given a limited definition of the index generator, here is a more general presentation:

Rule

In the expression r ⍳dim:

  • dim represents the shape of an array (the array itself need not actually exist); and

  • r is the set of indices of all items of that array.

The shape of r is given by dim and each item of r is the index (or the set of indices) of its own items.

Some examples should make this easier to understand. Let us take several arrays and inspect their shape:

contents
12
⍳⍴contents
1 2 3 4 5 6 7 8 9 10 11 12
contents[⍳⍴contents]
12 56 78 74 85 96 30 22 44 66 82 27
monMat
6 8
⍳⍴monMat
┌───┬───┬───┬───┬───┬───┬───┬───┐ │1 1│1 2│1 3│1 4│1 5│1 6│1 7│1 8│ ├───┼───┼───┼───┼───┼───┼───┼───┤ │2 1│2 2│2 3│2 4│2 5│2 6│2 7│2 8│ ├───┼───┼───┼───┼───┼───┼───┼───┤ │3 1│3 2│3 3│3 4│3 5│3 6│3 7│3 8│ ├───┼───┼───┼───┼───┼───┼───┼───┤ │4 1│4 2│4 3│4 4│4 5│4 6│4 7│4 8│ ├───┼───┼───┼───┼───┼───┼───┼───┤ │5 1│5 2│5 3│5 4│5 5│5 6│5 7│5 8│ ├───┼───┼───┼───┼───┼───┼───┼───┤ │6 1│6 2│6 3│6 4│6 5│6 6│6 7│6 8│ └───┴───┴───┴───┴───┴───┴───┴───┘
monMat[⍳⍴monMat]
January February March April May June
prod
5 2 12
⍳⍴prod
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬──────┬──────┬──────┐ │1 1 1│1 1 2│1 1 3│1 1 4│1 1 5│1 1 6│1 1 7│1 1 8│1 1 9│1 1 10│1 1 11│1 1 12│ ├─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼──────┼──────┼──────┤ │1 2 1│1 2 2│1 2 3│1 2 4│1 2 5│1 2 6│1 2 7│1 2 8│1 2 9│1 2 10│1 2 11│1 2 12│ └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴──────┴──────┴──────┘ ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬──────┬──────┬──────┐ │2 1 1│2 1 2│2 1 3│2 1 4│2 1 5│2 1 6│2 1 7│2 1 8│2 1 9│2 1 10│2 1 11│2 1 12│ ├─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼──────┼──────┼──────┤ │2 2 1│2 2 2│2 2 3│2 2 4│2 2 5│2 2 6│2 2 7│2 2 8│2 2 9│2 2 10│2 2 11│2 2 12│ └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴──────┴──────┴──────┘ ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬──────┬──────┬──────┐ │3 1 1│3 1 2│3 1 3│3 1 4│3 1 5│3 1 6│3 1 7│3 1 8│3 1 9│3 1 10│3 1 11│3 1 12│ ├─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼──────┼──────┼──────┤ │3 2 1│3 2 2│3 2 3│3 2 4│3 2 5│3 2 6│3 2 7│3 2 8│3 2 9│3 2 10│3 2 11│3 2 12│ └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴──────┴──────┴──────┘ ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬──────┬──────┬──────┐ │4 1 1│4 1 2│4 1 3│4 1 4│4 1 5│4 1 6│4 1 7│4 1 8│4 1 9│4 1 10│4 1 11│4 1 12│ ├─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼──────┼──────┼──────┤ │4 2 1│4 2 2│4 2 3│4 2 4│4 2 5│4 2 6│4 2 7│4 2 8│4 2 9│4 2 10│4 2 11│4 2 12│ └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴──────┴──────┴──────┘ ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬──────┬──────┬──────┐ │5 1 1│5 1 2│5 1 3│5 1 4│5 1 5│5 1 6│5 1 7│5 1 8│5 1 9│5 1 10│5 1 11│5 1 12│ ├─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼──────┼──────┼──────┤ │5 2 1│5 2 2│5 2 3│5 2 4│5 2 5│5 2 6│5 2 7│5 2 8│5 2 9│5 2 10│5 2 11│5 2 12│ └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴──────┴──────┴──────┘
prod[⍳⍴prod]
9 16 42 50 2 3 11 45 17 37 29 36 34 19 32 12 37 15 46 24 49 28 36 9 29 5 45 23 27 4 23 16 39 22 22 2 46 20 47 17 18 25 13 38 42 38 45 28 37 19 5 10 30 49 16 18 46 47 47 3 39 23 41 20 6 40 21 22 40 49 20 1 13 36 12 40 12 15 24 20 21 12 19 25 8 48 22 37 33 1 39 30 50 50 8 35 38 31 24 40 23 7 20 34 35 6 19 27 41 1 14 50 17 42 6 35 12 48 30 29

We can see that, more generally, an array is identical to itself indexed by Iota of its shape: data≡data[⍳⍴data]

4.19.5. Ravel With Axis#

Ravel can be used with an axis specifier, using the notation r←,[axis] data.

In this expression, axis can be:

  • an empty vector;

,[] chemistry
H 2 S O 4 C a C O 3 F e 2 O 3
  • a decimal scalar, adjacent to an axis of data; or

,[0.5] contents
12 56 78 74 85 96 30 22 44 66 82 27
  • a subset of the axes of data.

,[2 3] prod
9 16 42 50 2 3 11 45 17 37 29 36 34 19 32 12 37 15 46 24 49 28 36 9 29 5 45 23 27 4 23 16 39 22 22 2 46 20 47 17 18 25 13 38 42 38 45 28 37 19 5 10 30 49 16 18 46 47 47 3 39 23 41 20 6 40 21 22 40 49 20 1 13 36 12 40 12 15 24 20 21 12 19 25 8 48 22 37 33 1 39 30 50 50 8 35 38 31 24 40 23 7 20 34 35 6 19 27 41 1 14 50 17 42 6 35 12 48 30 29

4.19.5.1. Empty Axis#

If axis is empty, the result is obtained by appending a new dimension of size 1 to the list of data’s dimensions.

For example, if ⍴data is 12, then ⍴,[⍬] data will be 12 1:

contents
12 56 78 74 85 96 30 22 44 66 82 27
contents
12
,[] contents
12 56 78 74 85 96 30 22 44 66 82 27
⍴,[] contents
12 1

Or, if ⍴data is 3 5, then ⍴,[⍬] will be 3 5 1:

chemistry
H2SO4 CaCO3 Fe2O3
chemistry
3 5
,[] chemistry
H 2 S O 4 C a C O 3 F e 2 O 3
⍴,[] chemistry
3 5 1

4.19.6. Fractional Axis#

If axis is a fractional value, it is mandatory that it is “adjacent” to an existing dimension of data.

This means that for an array of rank 3, the axis must be a value between 0 and 4 (exclusive).

The result is derived from data by inserting a new dimension of size 1 in the list of data’s dimensions. The new dimension is inserted according to the value of axis.

It means that if

prod
5 2 12

then:

⍴,[0.5] prod
1 5 2 12
⍴,[1.5] prod
5 1 2 12
⍴,[2.5] prod
5 2 1 12
⍴,[3.5] prod
5 2 12 1

These conventions are totally consistent with those defined for some other functions like laminate or mix.

An example with a smaller array is also presented:

'PUB'
PUB
'PUB'
3
,[1.5] 'PUB'
P U B
,[0.5] 'PUB'
PUB
⍴,[0.5] 'PUB'
1 3

Notice how the result of ,[0.5] 'PUB' looks like that of 'PUB', but while the latter is a vector of length 3, the former is a matrix with 1 row and 3 columns.

The actual value of the axis specification is not used for anything other than determining where to insert the new dimension. This means that, for example, all expressions of the form ,[1.xxx] will have the same effect as the ,[1.5] that we used above:

,[1.5] 'PUB'
P U B
,[1.0123] 'PUB'
P U B

4.19.7. A List of Dimensions#

If axis is a subset of the axes of data, it is mandatory that they are contiguous and in ascending order. It means that for an array of rank 3, the list of axes can be [1 2], [2 3] or [1 2 3], but neither [1 3] nor [3 2], for example.

The result is obtained by unravelling data so that the dimensions mentioned in axis are merged into a single dimension.

For example, imagine that data is an array of shape 5 2 4 7:

data  5 2 4 70

We investigate the shapes of different ravels with lists of dimensions as axis specification:

⍴,[1 2] data        ⍝ 10 is 5×2
10 4 7
⍴,[2 3] data        ⍝ 8 is 2×4
5 8 7
⍴,[3 4] data        ⍝ 28 is 4×7
5 2 28
⍴,[1 2 3] data      ⍝ 40 is 5×2×4
40 7

4.19.8. Border Cases#

If axis is reduced to a single value, the operation returns data unchanged:

chemistry
H2SO4 CaCO3 Fe2O3
,[1] chemistry
H2SO4 CaCO3 Fe2O3
,[2] chemistry
H2SO4 CaCO3 Fe2O3

If axis contains the whole set of the axes of data, the operation is equivalent to a simple ravel with no axis specification, and gives a vector of the items of data:

,[1 2] chemistry
H2SO4CaCO3Fe2O3
,chemistry
H2SO4CaCO3Fe2O3

For these reasons, ravel with axes can only “collapse” dimensions of arrays of rank 2 or more.

4.20. Solutions#

The solutions we propose in the following pages are not necessarily the “best” ones; perhaps you will find other solutions that we have never considered. APL is a very rich language, and due to the general nature of its primitive functions and operators there are always plenty of different ways to express different solutions to a given problem. Which one is “the best” depends on many things, for example the level of experience of the programmer, the importance of system performance, the required behaviour in border cases, the requirement to meet certain programming standards and also personal preferences. This is one of the reasons why APL is so pleasant to teach and to learn!

We advise you to try and solve the exercises before reading the solutions!

Solution to Exercise 4.1

Copy each expression to the APL session and execute them. If you are surprised by an answer, analyse the expression step by step, starting from the right. For example, for the first expression:

3 × 2 + 6  3 × 2
6
3 × 2
6
6  6
0
2 + 0
2
3 × 2
6

And now the remainder of the expressions:

12 6 27  11 + 3
12 6 14
4 5 6  4 + 2 5 9 > 1 6 8
5 5 6
7  25 6 17 - (2 × 3) + 9 3 5
7 ¯3 6
((8 + 6) × 2 + 1) × 3 - 6 ÷ 3
42
(45) + 45
1  2  3  4  5  6  7
2
1  2  3  4  5  6  7
1
1 2 3  4 5 6
4 5 6

Solution to Exercise 4.2

2 2+2 2 is the addition of two vectors of size 2:

2 2+2 2
4 4

2+2 2+2 is the addition of 2 2 with 2, and then with 2 again:

2+2 2+2
6 6

Finally, although 2,2 gives the same array as writing 2 2 out, the fact that we are catenating means we have to evaluate the catenation also from right to left, making it happen after some of the additions have already been computed.

2+2,2+2
4 6
2,2+2,2
2 4 4

Solution to Exercise 4.3

a  8 2 7 5

Comparing 1+⍴a and ⍴a+1:

1+⍴a        ⍝ find the shape of `a` and then add 1 to that
5
a+1        ⍝ add 1 to all items of `a` and then find the shape of that
4

And now on to 1+⍳⍴a, ⍳¯1+⍴a and ⍳⍴a-1. The easiest way to understand these expressions is to figure out the order in which things are happening and inspecting the intermediate steps.

1+⍳⍴a
2 3 4 5
¯1+⍴a
1 2 3
⍳⍴a-1
1 2 3 4

Solution to Exercise 4.4

  • ((⍳4)-1)⌈3

To remove parenthesis, what you do is locate the outermost pair of parenthesis (which is ((⍳4)-1)), determine the function to which this pair of parenthesis is an argument (the ) and find the other argument to the same function (which is 3).

Then you check if you can swap the two arguments and, if so, remove the outermost pair of parenthesis:

((4)-1)  3
3 3 3 3
3  ((4)-1)
3 3 3 3
3  (4) - 1
3 3 3 3

Now if you wish to remove the parenthesis from (⍳4) we have to move it to the right of the subtraction, but - is not commutative so we have to employ a little trick to make sure the expression still evaluates to the same result:

3  (4) - 1
3 3 3 3
3  ¯1 + 4
3 3 3 3

Please note that this sort of thing is not always possible!

  • 7⌊(⍳9)⌈3

7 (9)  3
3 3 3 4 5 6 7 7 7
7 3  (9)
3 3 3 4 5 6 7 7 7
73⌈⍳9
3 3 3 4 5 6 7 7 7
  • 1+((⍳5)=1 4 3 2 5)×5

1+ ((5)=1 4 3 2 5) × 5
6 1 6 1 6
1+ 5 × ((5)=1 4 3 2 5)
6 1 6 1 6
1+ 5 × (5) = 1 4 3 2 5
6 1 6 1 6
1+5× 1 4 3 2 5 = (5)
6 1 6 1 6
1+5×1 4 3 2 5=⍳5
6 1 6 1 6

Solution to Exercise 4.5

There are a couple of ways to determine if a is a scalar or not. For example, we might check if its rank is 0 with 0=≢⍴a. We might also check if its shape matches the empty numeric vector with ⍬≡⍴a.

Solution to Exercise 4.6

A possible way to look at this is by realising that the expression we are about to create is supposed to give two different values depending on the value of a≥b. If a≥b is 1 the expression needs to evaluate to 7 and if not, it needs to evaluate to 3.

What we can do is write an expression that “returns at least 3, with a bonus 4 whenever a≥b”. From that perspective, we can do 3+(4×a≥b) which is the same as 3+4×a≥b.

Solution to Exercise 4.7

An example expression could be (a≠0)⍴3×b≠0. Here’s how to get there:

We have 3 restrictions, the first one being about the shape of the result and the other two about the value itself. Reading the first restriction and factoring it with the other two, we see that we should return an empty vector whenever a is zero and a numerical vector otherwise.

This means our expression will be of the form (a≠0)⍴(...). If a is zero, then a≠0 returns 0 and the right expression won’t matter, because we get an empty vector. If a is not zero, then a≠0 returns 1, and we leave the value of the right expression as is.

Now we only have to figure out what goes into (...), which should be an expression that evaluates to 0 if b is zero and 3 if b is not zero. We don’t need to care about enforcing a to be different from zero because if a is zero we will get an empty vector either way…

Thus this sub expression can be something like 3×b≠0, which evaluates to 3 or 0 depending on the value of b being zero or not.

Putting everything together we get (a≠0)⍴3×b≠0.

Solution to Exercise 4.8

  • replace with × or ;

  • replace with .

returns 1 if both of the arguments are 1 and 0 in any other case. Rephrasing this, we get that returns 0 whenever there is a 0 present. From this, we see that both × and are suitable replacements. × because whenever there is a 0, multiplication by 0 gives 0. A similar thing happens for because we are only dealing with arguments that are 0 or 1.

returns 1 whenever there is a 1 as argument, so we see that is a suitable replacement.

l  0 0 1 1
r  0 1 0 1
l  r
0 0 0 1
l × r
0 0 0 1
l  r
0 0 0 1
l  r
0 1 1 1
l  r
0 1 1 1

Solution to Exercise 4.9

g  1 1 1 0 0 1
m  0 0 1 1 0 1
d  1 0 1 0 1 0
gd
1 1 1 0 1 1
~gd
0 1 0 1 1 1
~g∨~d
0 0 0 0 1 0
d∧~g
0 0 0 0 1 0
gmd
1 0 1 0 0 1
(~d)(~g)
0 0 0 1 0 0
(mg)=(md)
0 0 1 0 1 0
(mg)(md)
1 0 0 1 1 0

Solution to Exercise 4.10

0 < 0  0 = 0  0 > 0
1
'sugar'  'salt'
1 0 0 1 0

Above we check which letters of 'sugar' also show up in 'salt'.

11  '11'
1 1

Here we compare each character of '11' with the number 11.

'14'  '41'
2 1

And here we find the position of each character of '41' in '14'.

Solution to Exercise 4.11

A sensible way to approach this problem is to first compare the string with the character 'e' and then sum the 1s that show up:

text  'The silence of the sea'
+/ 'e'=text
5

Solution to Exercise 4.12

You may have come up with expressions that are similar to these ones or maybe completely different! We show some possibilities below.

We included different types of vectors so that you could be sure your solutions also work for all sorts of different vectors.

vec  1 2 3 4 5 6
/ vec
6
vec  (1 2) 3 (4 5) 6
vec[vec]
6
vec  0 (50) (3 40) (1 2 30)
((vec)=⍳≢vec)/vec
┌─────┐ │0 0 0│ │0 0 0│ └─────┘
(vec)vec
┌─────┐ │0 0 0│ │0 0 0│ └─────┘

Solution to Exercise 4.13

Notice how 2 7 gives the first 2 elements of z, which are 1 and 7.

Then

      z = 9
0
0
1
0

gives both the shape of z (a column with 4 rows) and the third element which is 9. The missing clue gives that the elements of z sum up to 20, but we already have 1, 7 and 9 which sum to 17, so the remaining element of z is a 3:

z  4 11 7 9 3
1 7 9 3
2  z
1 7
+ z
20
z = 9
0 0 1 0

Solution to Exercise 4.14

      z = 0
0 1 0 0
1 0 0 1

gives the shape of z and some of its values:

? 0 ? ?
0 ? ? 0

Now +/[1] z gives the sums of the columns, from which we extract

8 0 ? 5
0 7 ? 0

Finally, +/[2] z gives the sums of the rows, from which we get that the missing ? are 7 and -1:

8 0  7 5
0 7 ¯1 0
z  2 48 0 7 5 0 7 ¯1 0
8 0 7 5 0 7 ¯1 0
z = 0
0 1 0 0 1 0 0 1
+/[2] z
20 6
+/[1] z
8 7 6 5

Solution to Exercise 4.15

Positions can be retrieved with where:

text  'The silence of the sea'
'e'=text
3 8 11 18 21

Solution to Exercise 4.16

There are a couple of different ways to solve this problem. From an indexing perspective, we might want to generate all the odd numbers up to, and including, the length of the vector:

vec  1 2 3 4 5 6 7 8 9
vec[¯1+2×⍳⌈(vec)÷2]
1 3 5 7 9

This works by first figuring out how many indices we are going to need:

(vec)÷2
5

Then generating 5 consecutive numbers and adjusting them so they become the odd numbers:

¯1+2×⍳5
1 3 5 7 9

Another possibility is to compress the odd positions:

vec  'CROANGGARYANTLULLBAPTWIZOSNSSB'
(2|⍳≢vec)/vec
CONGRATULATIONS

Another really ingenious solution makes use of the fact that reshape reuses the right argument if needed, and so we can just compress by reshaping 1 0 to the correct length:

vec  (1 2) (2 3) (4 5 6) (6 9 42 1024)
((vec)1 0)/vec
┌───┬─────┐ │1 2│4 5 6│ └───┴─────┘

Solution to Exercise 4.17

Ravel will take a 3D array and create a long vector with all its items, so we just have to count them:

≢,prod
120

Alternatively, we can use some mathematics and realise that there are as many items in prod as its shape allows, where multiplying the dimensions together will yield the total. Think of it like computing the area of a rectangle for a 2D array and the volume of a rectangular prism for a 3D array:

prod
5 2 12
×/prod
120

Solution to Exercise 4.18

This is a simple exercise in compressing with a Boolean mask. We just create a mask with all the numbers we want to keep:

ns  10+⍳30            ⍝ just a vector to exemplify with
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
((ns<30)ns20)/ns
20 21 22 23 24 25 26 27 28 29

Be careful with using the correct Boolean operator. Swapping them around might create an expression which preserves the vector as-is:

((ns<30)ns20)/ns     ⍝ swapped ∧ with ∨
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40

Solution to Exercise 4.19

vec
4
vec
4

The two expressions have the same value but they have different shapes. While ≢vec is a scalar:

⍴≢vec

⍴vec is a vector of length 1:

⍴⍴vec
1

Solution to Exercise 4.20

For this exercise we just have to revisit the rule for how depth works and work our way inside out.

A scalar has depth 0

42
0

and a simple vector has depth 1

42 41 40 39
1

because all its items are scalars of depth 0. Similarly for a simple matrix. Even though its rank is larger than the rank of a vector, the matrix only contains scalars of depth 0 and thus its depth is also 1:

2 242 41 40 39
1

For uneven nested vectors we just compute the largest depth, add 1 and then return it as a negative number. Since 1 (2 3) (4 5) contains scalars (of depth 0) and vectors (of depth 1) its depth should be -2:

1 (2 3) (4 5)
¯2

On the other hand, (1 2) (3 4 5) (6 7 8 9) is a vector of vectors, and thus its depth is 2:

(1 2) (3 4 5) (6 7 8 9)
2

Computing the depth of 1 (2 (3 (4 (5 (6 7))))) is just a matter of counting carefully. The inner (6 7) has depth 1, 5 (6 7) has depth -2, 4 (5 (6 7)) has depth -3, …, up until

1 (2 (3 (4 (5 (6 7)))))
¯6

The final example is to try and throw you off by including items that have higher ranks, but still the same depth. A simple matrix has depth 1, so a vector of simple matrices is going to have depth 2:

(2 24) (2 2⍴⍳47) (3 6⍴⍳18)
2

Solution to Exercise 4.21

This task of ensuring all numbers in an array are above a minimum threshold and below a maximum threshold is sometimes called clipping and is easy to achieve in APL with the scalar and functions. We create a dummy matrix to demonstrate:

⎕RL  73
m  15+?3 320
24 31 25 33 17 18 26 28 32

We want m’s values to be at least 20, so numbers below should become 20:

20m
24 31 25 33 20 20 26 28 32

Similarly for numbers above 30:

3020m
24 30 25 30 20 20 26 28 30

Solution to Exercise 4.22

The challenge of this exercise was to spot the error before trying to run the expression on the interpreter and reading its error message.

  • 3+(5-(6+2)×4

Looking close enough, we see there is an extra ( that never got closed, getting us a SYNTAX ERROR.

3+(5-(6+2)×4
SYNTAX ERROR: Unpaired parenthesis
      3+(5-(6+2)×4
        ∧
  • 121÷(⍳4)-3

This one won’t work because there is a hidden division by 0 in there:

(4)-3
¯2 ¯1 0 1
121÷(4)-3
DOMAIN ERROR: Divide by zero
      121÷(⍳4)-3
         ∧
  • (¯X+5)*2

The error in this expression is the usage of ¯ with a variable name. ¯ can only be used when writing out numbers. If you want to negate the value of a variable, use the function negate -.

For us to show the appropriate error, we need to define X to have some value.

X  42
(¯X+5)*2
SYNTAX ERROR
      (¯ X+5)*2
       ∧

The “Invalid token” mentioned is the ¯ next to a variable name, when it should be next to a number literal.

  • ⍴4 5 6+2 3-1

The problem with this expression is that we are using scalar functions with vectors whose lengths aren’t compatible. The 2 3-1 works because - knows how to subtract a number from a vector, but then 4 5 6+1 2 will attempt to add two vectors of incompatible lengths and fail:

4 5 6+2 3-1
LENGTH ERROR: Mismatched left and right argument shapes
      ⍴4 5 6+2 3-1
            ∧
  • ⍳3-5

Index generator expects a vector of non-negative numbers as argument and so a simple DOMAIN ERROR is issued:

3-5
DOMAIN ERROR
      ⍳3-5
      ∧
  • ⍸1 0 2 ¯1

Similarly, where only expects non-negative integers in its argument array, so the ¯1 is an issue there:

1 0 2 ¯1
DOMAIN ERROR: Where right argument must be non-negative
      ⍸1 0 2 ¯1
      ∧

Solution to Exercise 4.23

This “vector of 17 numbers, the first being 23, with each subsequent number being equal to the preceding one plus 11” is called an arithmetic progression in mathematics. Generating them is really easy if you use the algorithm we showed earlier.

We start by generating a vector of consecutive numbers with the appropriate length:

17
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

Then we multiply the whole sequence by the spacing we want there to be between each number:

11×⍳17
11 22 33 44 55 66 77 88 99 110 121 132 143 154 165 176 187

Then we add/subtract a number to the sequence so that the first element matches the required value. The first number is currently 11 and we want it to be 23, so we have to add 12:

12+11×⍳17
23 34 45 56 67 78 89 100 111 122 133 144 155 166 177 188 199

Notice the final expression is equivalent to the algorithm presented in Section 4.15.1, and working our way up to the expression is easier than trying to memorise an algorithm!

23+11×(17)-1
23 34 45 56 67 78 89 100 111 122 133 144 155 166 177 188 199

Solution to Exercise 4.24

The first sensible thing to do is separate the products from the quantities. We can do this by creating a matrix with 2 columns:

wannaBuy  37 1 70 20 19 2 82 5 23 10
m  ((0.5×≢wannaBuy),2)wannaBuy
37 1 70 20 19 2 82 5 23 10

Now the first column contains the codes and the second column contains the quantities. We chose 2 columns instead of 2 rows because of the way fills in the matrix: from left to right, top to bottom.

The next thing to do is map the product codes to their prices:

pcodes  56 66 19 37 44 20 18 23 68 70 82
prices   9 27 10 15 12  5  8  9 98  7 22
pos  pcodesm[;1]
4 10 3 11 8

Now that we have the positions of the products the customer wants, we can find the prices, multiply them by the quantities and sum everything:

+/prices[pos]×m[;2]
375

Solution to Exercise 4.25

⎕RL  73
tickets  ?1000999999
sold  tickets[(800+?200)?1000]
ours  sold[(?200)?≢sold]
winners  tickets[100?1000]
prizes  ?1001000

The unsold tickets are the tickets without the ones that have been sold:

tickets ~ sold
636802 512379 327584 742744 867694 518416 837522 756393 875860 650618 734820 850511 269877 919436 540758 983293 504930 693 521 242886 704993 445662 713406 614389 483967 897435 610802 578109 768129 383069 395407 7478 420111 30447 132341 519 031 539593 295049 52849 187875 820478 641186 686560 450428 313323 281393 935843 861713 601571 508158 96073 233311 48 7348 757868 57876 971533 996215 816637 140520 378089 292169 178757 246775 246429 316212 309252 962321 73397 326613 8 63219 442574 838820 560717 872464 544884 211478 421320 670853 933120 929562 29943 872039 24194 313244 525545 974249 391069 229590 164488 988529 891749 309984 384385 896675 903217 891047 255077 547699 378376 611404 156401 663154 1014 48 312591 50864 568638 231875 952662 701228 797311 212429 248189 521779 858987 325307 775909 781609 91337 354366 309 96 628490 407462 6628 93348 815928 61755 195544 16935 236409 889935 629654 86574 345820 734765 996468 387036 865286 822609 253808 263675 816052 567659 493785 574772

Similarly, we can find the winners’s tickets without the ones that have been sold:

winners ~ sold
983293 73397 140520 7478 212429 670853 628490 701228 313323 384385 574772 327584 248189 816637 971533 962321

So we can see some of the winning tickets were not sold.

We have

ours
160

tickets but not all of them are winning tickets. However, we can easily see which of our tickets are winning tickets by checking which ones are members of the vector with the winning tickets:

ourswinners
0 0 1 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 1 0 0 1 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

If we want to know how many tickets are winning tickets, all we have to do is sum up the 1s in the vector above.

+/ourswinners
16

Finally, to find our earnings we need to connect ours to the prizes. The easiest way to do so is by using compress after finding out which winners are also ours:

+/ (winnersours)/prizes
7043

Another (more convoluted) possibility would be to index into the prizes with the winners that were also ours. For example, finding the positions of ours in the winners:

pos  winners  ours

Now notice we cannot index immediately into prizes:

prizes[pos]
INDEX ERROR
      prizes[pos]
            ∧

This is because some of our tickets are not winners and hence their index is one too large. To circumvent this we can extend prizes to have a trailing 0, so that our tickets that are not winners will contribute with 0 to the final sum:

prizes  prizes, 0
+/ prizes[pos]
7043

Solution to Exercise 4.26

Computing divisors of a number n amounts to finding the numbers m for which m|n is 0. First we compute the vector of all candidates:

n  24
m  n
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

Now we compute the residues with, you guessed it, residue:

r  m|n
0 0 0 0 4 0 3 0 6 4 2 0 11 10 9 8 7 6 5 4 3 2 1 0

From these, we want the 0s because those represent numbers in m that divide evenly into m. Then we can use to convert them back to the original integers:

0=r
1 2 3 4 6 8 12 24