# 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
```