# 3 Functions

## 3.1 Compress

Compress (IBM (1988) , p. 91–92) is defined as the Replicate operator L/R in the special case that L is binary. So look under Replicate for the definition.

## 3.2 Decode

The decode dyadic operator L⊥R is also known as base value (Helzer (1989), p. 17-21). If L is scalar and R is a vector, then L⊥R is the polynomial $$r_1x^{m-1}+r_{2}x^{m-2}+\cdots+r_m$$ evaluated at L. This means that if the $$r_i$$ are nonnegative integers less than L, then L⊥R gives the base-10 equivalent of the base-L number R.

Normally, however, and in our R implementation, the arguments L and R are vectors of the same length. This is also because for scalar L the expression L⊥R is expanded to ((⍴R)⍴L)⊥R If L and R are vectors of the same length then decode returns the index of element A[R] in an array A with dim(A)=L.

Obviously the R implementation, which uses colum-major ordering, will give results different from the APL implementation. In APL the expression 3 3 3⊥1 2 3 evaluates to 18, while aplDecode(1:3, rep(3,3)) gives 22. Also note the order of the arguments is interchanged. Also note that if x and y are of length m, and y has all elements equal to z, then aplDecode(x, rep(y, length(x))) is the value of the polynomial $p(u)=1+(x_1-1)u^0+(x_2-1)u^1+\cdots+(x_m-1)u^{m-1}$ evaluated at $$u=z$$.

for (k in 1:3) for (j in 1:3) for (i in 1:3)
print (aplDecode (c(i,j,k), c(3,3,3)))
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
## [1] 6
## [1] 7
## [1] 8
## [1] 9
## [1] 10
## [1] 11
## [1] 12
## [1] 13
## [1] 14
## [1] 15
## [1] 16
## [1] 17
## [1] 18
## [1] 19
## [1] 20
## [1] 21
## [1] 22
## [1] 23
## [1] 24
## [1] 25
## [1] 26
## [1] 27

## 3.3 Drop

See Helzer (1989), p. 49–51. If L is a positive integer and R is a vector then L↓R drops the first L elements. If L is a negative integer the last L elements are dropped. If R is an array, then L must have ⍴⍴R elements. Depending on whether the elements or L are positive or negative, the L first or last slices of the dimension are dropped.

Our R implementation again interchanges the two arguments. Note that in R the default on subsetting arrays is drop = TRUE. In our implementations the default is always drop = FALSE. Note that more general subsetting can be done with aplSelect().

So for a vector

aplDrop(1:10,3)
## [1]  4  5  6  7  8  9 10
aplDrop(1:10,-3)
## [1] 1 2 3 4 5 6 7

and for an array

aplDrop(a,c(1,0,1), drop = TRUE)
##      [,1] [,2] [,3]
## [1,]    8   14   20
## [2,]   10   16   22
## [3,]   12   18   24
aplDrop(a,c(-1,-1,0), drop = TRUE)
##      [,1] [,2] [,3] [,4]
## [1,]    1    7   13   19
## [2,]    3    9   15   21

## 3.4 Encode

Encode, also known as representation is the inverse of decode (Helzer (1989), p. 17–21). L⊤R takes a radix vector L and a number R and returns the array indices corresponding to cell L in an array with shape ⍴A.

for (i in 1:27)
print (aplEncode (i, c(3,3,3)))
## [1] 1 1 1
## [1] 2 1 1
## [1] 3 1 1
## [1] 1 2 1
## [1] 2 2 1
## [1] 3 2 1
## [1] 1 3 1
## [1] 2 3 1
## [1] 3 3 1
## [1] 1 1 2
## [1] 2 1 2
## [1] 3 1 2
## [1] 1 2 2
## [1] 2 2 2
## [1] 3 2 2
## [1] 1 3 2
## [1] 2 3 2
## [1] 3 3 2
## [1] 1 1 3
## [1] 2 1 3
## [1] 3 1 3
## [1] 1 2 3
## [1] 2 2 3
## [1] 3 2 3
## [1] 1 3 3
## [1] 2 3 3
## [1] 3 3 3

## 3.5 Expand

Expand (Helzer (1989), p. 64–66) L\R replaces slices of a vector or an array by zeroes. In APL we use a boolean vector for L, and an array for R. If R is a vector then the number of elements of L equal to one (i.e. TRUE) should be equal to the length of R. Then L\R produce a vector of length ⍴L with the elements of $$R$$ in the places where L is one, and zeroes elsewhere. In our function aplExpand() we again reverse the order of the arguments. The second argument in the R verson can be both logical or binary.

aplExpand(1:3, c(1,0,0,0,1,1))
## [1] 1 0 0 0 2 3

For an array expand takes an optional axis argument. For a matrix, for example, axis=1 expands rows (i.e. inserts row of zeroes at specfied places), while axis=2 expands columns. This generalizes to multidimensional arrays, where there are just more axis to consider, but any one of them can be expanded.

aplExpand(matrix(1,2,3), c(1,0,0,1), axis = 1)
##      [,1] [,2] [,3]
## [1,]    1    1    1
## [2,]    0    0    0
## [3,]    0    0    0
## [4,]    1    1    1
aplExpand(matrix(1,2,3), c(1,1,0,1,0), axis = 2)
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    1    1    0    1    0
## [2,]    1    1    0    1    0

## 3.6 Get

There is no APL function corresponding to get. We wrote aplGet() as a simple utility to get an element out of an array,

a <- array(1:24, c(2,3,4))
aplGet (a, c(2,2,2))
## [1] 10
aplGet (a, aplEncode (14, c(2,3,4)))
## [1] 14

## 3.7 Inner Product

APL has a generalized inner product (Helzer (1989), p. 100-103), which we write as Lf.gR, where f and g are any scalar dyadic functions. In obvious notation the $$f,g$$ inner product of two vector $$x$$ and $$y$$ of length $$n$$ is $$f(g(x_1,y_1),g(x_2,y_2),\cdots,g(x_n,y_n))$$. If L and R are arrays, then the last item of ⍴L must be equal to the first item of ⍴R and the generalized inner product operates along that common dimension. The default for g is multiplicaton, and for f is addition, leading to the ordinary inner product and to matrix multiplication.

Of course there are many examples we can give.

x <- matrix (1:12,4,3)
y <- matrix (1:12,3,4)
aplInnerProduct (x, y)
##      [,1] [,2] [,3] [,4]
## [1,]   38   83  128  173
## [2,]   44   98  152  206
## [3,]   50  113  176  239
## [4,]   56  128  200  272
h <- function (x, y) ifelse (x == y, 1, 0)
aplInnerProduct (x, y, h, "+")
##      [,1] [,2] [,3] [,4]
## [1,]    1    1    1    0
## [2,]    0    0    0    0
## [3,]    0    0    0    0
## [4,]    0    1    1    1
a <- array (1:24, c(2,3,4))
b <- rep (1, 4)
aplInnerProduct (a, b)
##      [,1] [,2] [,3]
## [1,]   40   48   56
## [2,]   44   52   60

## 3.8 Join

Join catenates two arrays of the same rank, creating another larger array of that rank (Helzer (1989), p. 104–110). If L and R are vectors L,R simply concatenates, same as c() in R. If L and R are arrays they can be catenated along axis k if (⍴L)[-k] = (⍴R)[-k]. Thus matrices with the same number of columns can be stacked on top of each other, and matrices with the same number of rows can be stacked next to each other. Same for higher-dimensional arrays.

In APL there are some special rules for handling scalar arguments (they get reshaped accordingly first), and even for fractional axis parameters, which can be used for lamination of arrays with different rank. We have not implemented these more complicated optiosn in our function aplJoin().

x<-1:3
y<-3:1
aplJoin(x,y)
## [1] 1 2 3 3 2 1
x <- matrix (1:12, 3, 4)
y <- matrix (1:8, 2, 4)
aplJoin (x, y, axis = 1)
##      [,1] [,2] [,3] [,4]
## [1,]    1    4    7   10
## [2,]    2    5    8   11
## [3,]    3    6    9   12
## [4,]    1    3    5    7
## [5,]    2    4    6    8
a <- array (1:24, c(2, 3, 4))
b <- array (1:30, c(2, 3, 5))
dim(aplJoin(a, b, axis = 3))
## [1] 2 3 9

## 3.9 Member Of

The member of function in APL L∈R returns a binary array with the shape of L. Array R can be of any shape. If an element of L occurs in R then the corresponding element in L∈R is one, otherwise it is zero. Our function aplMemberOf() returns an array or vector of the same dimension or length as the first argument. The test for equality is a simple ==, so integers and doubles can be compared.

a <- array (1:24, c(2,3,4))
aplMemberOf(a, c(1,2,15,25))
## , , 1
##
##      [,1] [,2] [,3]
## [1,]    1    0    0
## [2,]    1    0    0
##
## , , 2
##
##      [,1] [,2] [,3]
## [1,]    0    0    0
## [2,]    0    0    0
##
## , , 3
##
##      [,1] [,2] [,3]
## [1,]    0    1    0
## [2,]    0    0    0
##
## , , 4
##
##      [,1] [,2] [,3]
## [1,]    0    0    0
## [2,]    0    0    0

## 3.10 Outer Product

The APL outer product L ∘.f R is the same as the outer product in R (Helzer (1989), p. 147–149). Both L and R can be arbitrary arrays, and the result is an array with shape (⍴L),⍴R, formed by applying the dyadic function f to each combination of elements of L and R. Thus our aplOuterProduct() just calls the R function outer() on its arguments.

x <- matrix (1:4, 2,2)
y <- matrix (1:4, 2,2)
aplOuterProduct (x, y, "+")
## , , 1, 1
##
##      [,1] [,2]
## [1,]    2    4
## [2,]    3    5
##
## , , 2, 1
##
##      [,1] [,2]
## [1,]    3    5
## [2,]    4    6
##
## , , 1, 2
##
##      [,1] [,2]
## [1,]    4    6
## [2,]    5    7
##
## , , 2, 2
##
##      [,1] [,2]
## [1,]    5    7
## [2,]    6    8

## 3.11 Ravel

The APL function ravel reshapes its argument into a vector. So ,R is identical to as.vector() in R, and aplRavel() is just a call to as.vector().

aplRavel(array(1:24, c(2,3,4)))
##  [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
## [24] 24

## 3.12 Rank

There is no APL function rank, but we just implemented aplRank() as a convenient shorthand for ⍴⍴R. In R we just call length(dim()).

aplRank(array(1:24, c(2,3, 4)))
## [1] 3

## 3.13 Reduce

It is time for a quote from Helzer (1989), p. 165. “Summing a list of numbers is a common programming task. In APL this, and much more, is accomplished using the reduction operator. In APL terminology an operator modifies the action of a function. Standard APL has four operators, inner product (f.g), outer product (∘.f), reduction (f/), and scan (f\). Inner product creates a new dyadic function out of two scalar dyadic functions f and g. Outer product creates a new dyadic functionout of a scalar dyadic function f. Reduction and scan both create a new monadic function out of a scalar dyadic function f.” In APL there are two operators ⌿, which reduces along the first axis and /, which reduces along the last axis. Reducing along the k-th axis is f/[k].

Our R version aplReduce() of reduce takes three arguments: the array, the axes to reduce over, and the dyadic function used for reduction (by default addition). Note that the second argument can be a vector with more than one axis, in which case we reduce over all those axes.

a <- array(1:24, c(2,3,4))
aplReduce (a, c(1,2), max)
## [1]  6 12 18 24
aplReduce (a, 1, function (x, y) ifelse (x > y, x, y))
##      [,1] [,2] [,3] [,4]
## [1,]    2    8   14   20
## [2,]    4   10   16   22
## [3,]    6   12   18   24

## 3.14 Replicate

If L and R in replicate L/R are vectors of the same length, then the result is a vector of length +/L in which element $$r[i]$$ is repeated $$l[i]$$ times. If L is binary then some elements will be deleted, and replicate is called compress.

aplReplicate(1:3,c(3,1,3))
## [1] 1 1 1 2 3 3 3
aplReplicate(1:10,rep(c(0,1),5))
## [1]  2  4  6  8 10

This can be extended to higher dimensional arrays in the usual way by specifying the axis along which to replicate. By default, as in APL, we select the last axis. In APL L⌿R is used to replicate along the first axis. Our function aplReplicate() covers both cases.

a <- array(1:24,c(2,3,4))
aplReplicate(aplReplicate (a, c(2,2), 1), c(0,2,0), 2)
## , , 1
##
##      [,1] [,2]
## [1,]    3    3
## [2,]    3    3
## [3,]    4    4
## [4,]    4    4
##
## , , 2
##
##      [,1] [,2]
## [1,]    9    9
## [2,]    9    9
## [3,]   10   10
## [4,]   10   10
##
## , , 3
##
##      [,1] [,2]
## [1,]   15   15
## [2,]   15   15
## [3,]   16   16
## [4,]   16   16
##
## , , 4
##
##      [,1] [,2]
## [1,]   21   21
## [2,]   21   21
## [3,]   22   22
## [4,]   22   22
aplReshape (c(1,2), c(2,2,2))
## , , 1
##
##      [,1] [,2]
## [1,]    1    1
## [2,]    2    2
##
## , , 2
##
##      [,1] [,2]
## [1,]    1    1
## [2,]    2    2
aplReshape (array(1:24, c(2,3,4)), c(2,2))
##      [,1] [,2]
## [1,]    1    3
## [2,]    2    4

## 3.15 Rotate

Rotate (Helzer (1989), p. 191–193) cyclically shifts the elements of a vector or array dimension. In APL we write either L⌽R or L⊖R, depending on whether rotation is along the first or the last axis.

For vectors aplRotate(x, k) with positive k takes the first k elements of x and moves them to the end, with negative k it takes the last k elements and moves them to the front.

aplRotate(1:6, 2)
## [1] 3 4 5 6 1 2
aplRotate(1:6, -2)
## [1] 5 6 1 2 3 4

For a matrix we can shift rows or columns. For a matrix R the expression L⌽R, or aplRotate (R, L, axis = 2), shifts the elements of rows, with within each row possibly a different shift. Thus the number of elements of L must be the number of rows of R. If L is a scalar, then it is basically first blown up into a vector, and the same shift L is applied to each row (which means that a column is shifted). If axis = 1 then elements within columns are shifted. By default the argument axis equals aplRank(a).

a <- cbind(1:5, matrix (0,5,4))
aplRotate (a, 2)
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    0    0    0    1    0
## [2,]    0    0    0    2    0
## [3,]    0    0    0    3    0
## [4,]    0    0    0    4    0
## [5,]    0    0    0    5    0
aplRotate (a, -c(0,1,2,3,4))
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    1    0    0    0    0
## [2,]    0    2    0    0    0
## [3,]    0    0    3    0    0
## [4,]    0    0    0    4    0
## [5,]    0    0    0    0    5
aplRotate (a, 2, 1)
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    3    0    0    0    0
## [2,]    4    0    0    0    0
## [3,]    5    0    0    0    0
## [4,]    1    0    0    0    0
## [5,]    2    0    0    0    0

For arrays matters become more complicated. To rotate R along k we need dim(L) to be dim(R)[-k]. Then each element in L defines a slice of R that is a vector of length dim(R)[k]. That vector then gets rotated using the positive or negative value of the corresponding element of L.

a <- array(1:24, c(2,3,4))
aplRotate (a, 1, 1)
## , , 1
##
##      [,1] [,2] [,3]
## [1,]    2    4    6
## [2,]    1    3    5
##
## , , 2
##
##      [,1] [,2] [,3]
## [1,]    8   10   12
## [2,]    7    9   11
##
## , , 3
##
##      [,1] [,2] [,3]
## [1,]   14   16   18
## [2,]   13   15   17
##
## , , 4
##
##      [,1] [,2] [,3]
## [1,]   20   22   24
## [2,]   19   21   23
b <- matrix (c(0,0,0,1,1,1), 2, 3, byrow = TRUE)
aplRotate (a, b, 3)
## , , 1
##
##      [,1] [,2] [,3]
## [1,]    1    3    5
## [2,]    8   10   12
##
## , , 2
##
##      [,1] [,2] [,3]
## [1,]    7    9   11
## [2,]   14   16   18
##
## , , 3
##
##      [,1] [,2] [,3]
## [1,]   13   15   17
## [2,]   20   22   24
##
## , , 4
##
##      [,1] [,2] [,3]
## [1,]   19   21   23
## [2,]    2    4    6

## 3.16 Scan

The APL operator scan does not change dimension (Helzer (1989), p. 195–197). The expression f\R for a vector R can be defined in terms of reduction. It produces the vector with elements f/R[1] , f/R[1 2] , f/R[1 2 3], …. Thus it generalizes R funcions such as cumsum(), cumprod(), and so on. For a matrix f\R scans the rows and f\[1]R or f⍀R scans the columns. For arrays we again need an axis to expand along.

a<-matrix(1:9,3,3)
aplScan(a, 1, "+")
##      [,1] [,2] [,3]
## [1,]    1    4    7
## [2,]    3    9   15
## [3,]    6   15   24
aplScan(a, f = min)
##      [,1] [,2] [,3]
## [1,]    1    1    1
## [2,]    2    2    2
## [3,]    3    3    3
a<-array(1:24,c(2,3,4))
aplScan(a, 3, "*")
## , , 1
##
##      [,1] [,2] [,3]
## [1,]    1    3    5
## [2,]    2    4    6
##
## , , 2
##
##      [,1] [,2] [,3]
## [1,]    7   27   55
## [2,]   16   40   72
##
## , , 3
##
##      [,1] [,2] [,3]
## [1,]   91  405  935
## [2,]  224  640 1296
##
## , , 4
##
##      [,1]  [,2]  [,3]
## [1,] 1729  8505 21505
## [2,] 4480 14080 31104

## 3.17 Select

Select is not an APL function. Our function aplSelect() has an array a as its first argument, and a list of aplRank(a) vectors of integers as it second element. It then selects the corresponding rows, columns, slices, and so on.

a <- array(1:24, c(2,3,4))
aplSelect(a, list(1,c(1,2),c(3,4)))
##      [,1] [,2]
## [1,]   13   19
## [2,]   15   21
aplSelect(a, list(1,c(1,2),c(3,4)), drop = FALSE)
## , , 1
##
##      [,1] [,2]
## [1,]   13   15
##
## , , 2
##
##      [,1] [,2]
## [1,]   19   21

## 3.18 Set

There is no APL function corresponding with set. We wrote aplSet() as a simple utility to set an element of an array to a value.

a <- array(1:12, c(2,3,2))
aplSet (a, 11, c(2,2,2))
## , , 1
##
##      [,1] [,2] [,3]
## [1,]    1    3    5
## [2,]    2    4    6
##
## , , 2
##
##      [,1] [,2] [,3]
## [1,]    7    9   11
## [2,]    8   11   12

## 3.19 Shape

Shape is the monadic version of ⍴, while reshape is the dyadic version. Shape gives the dimensions of an array, an reshape modifies them. Of course aplShape() is just a wrapper for the basic R function dim().

a <- array(1:12, c(2,3,2))
aplShape(a)
## [1] 2 3 2
aplShape(aplShape(a))
## [1] 3

## 3.20 Take

Take L↑R extracts items from vectors and arrays, in the same way as drop L↓R deletes items. If R is a vector then L items from the beginning of R are taken if L is positive, and L items from the end if L is negative. If R is an array, then L must have ⍴⍴R elements. Depending on whether the elements or L are positive or negative, the L first or last parts of the dimension are taken

Our R implementation again interchanges the two arguments. Note that in base R the default on subsetting arrays is drop = TRUE. In our implementations the default is always drop = FALSE. Both aplTake() and aplDrop() are implemented by constructing a suitable list of index vectors for aplSelect(). Note that more general subsetting can be done with aplSelect(). Note that

So for a vector

aplTake(1:10,3)
## [1] 1 2 3
aplTake(1:10,-3)
## [1]  8  9 10

and for an array

a <- array(1:24, c(2,3,4))
aplTake(a, c(2,3,2), drop = TRUE)
## , , 1
##
##      [,1] [,2] [,3]
## [1,]    1    3    5
## [2,]    2    4    6
##
## , , 2
##
##      [,1] [,2] [,3]
## [1,]    7    9   11
## [2,]    8   10   12
aplTake(a, c(2,-2,1), drop = TRUE)
##      [,1] [,2]
## [1,]    3    5
## [2,]    4    6

## 3.21 Transpose

APL has both a monadic ⍉R and a dyadic L⍉R transpose. This APL transpose has a somewhat tortuous relationship with aperm() in R.

The monadic aplTranspose(a) and aperm(a) are always the same, they reverse the order of the dimensions.

If x is a permutation of 1:aplRank(a), then aperm(a,x) is actually equal to aplTranspose(a,order(x)) For permutations we could consequently define aplTranspose(a,x) simply as aperm(a,order(x)) (which would undoubtedly be more efficient as well).

a <- array(1:24, c(2, 3, 4))
aplTranspose (a)
## , , 1
##
##      [,1] [,2] [,3]
## [1,]    1    3    5
## [2,]    7    9   11
## [3,]   13   15   17
## [4,]   19   21   23
##
## , , 2
##
##      [,1] [,2] [,3]
## [1,]    2    4    6
## [2,]    8   10   12
## [3,]   14   16   18
## [4,]   20   22   24
aplTranspose (a, c(2,1,3))
## , , 1
##
##      [,1] [,2]
## [1,]    1    2
## [2,]    3    4
## [3,]    5    6
##
## , , 2
##
##      [,1] [,2]
## [1,]    7    8
## [2,]    9   10
## [3,]   11   12
##
## , , 3
##
##      [,1] [,2]
## [1,]   13   14
## [2,]   15   16
## [3,]   17   18
##
## , , 4
##
##      [,1] [,2]
## [1,]   19   20
## [2,]   21   22
## [3,]   23   24

If x is not a permutation, then aperm(a,x) is undefined, but aplTranspose(a,x) can still be defined in some cases. If x has aplRank(a) elements equal to one of 1:m, with each of 1:m occurring a least once, then aplTranspose(a,x) has rank m. If an integer between 1 and m occurs more than once, then the corresponding diagonal of a is selected.

a
## , , 1
##
##      [,1] [,2] [,3]
## [1,]    1    3    5
## [2,]    2    4    6
##
## , , 2
##
##      [,1] [,2] [,3]
## [1,]    7    9   11
## [2,]    8   10   12
##
## , , 3
##
##      [,1] [,2] [,3]
## [1,]   13   15   17
## [2,]   14   16   18
##
## , , 4
##
##      [,1] [,2] [,3]
## [1,]   19   21   23
## [2,]   20   22   24
aplTranspose (a, c(2,2,1))
##      [,1] [,2]
## [1,]    1    4
## [2,]    7   10
## [3,]   13   16
## [4,]   19   22