5.3 A la APL - naver/lispe GitHub Wiki

Operators à la APL

back

We have implemented some operators, which draw their inspiration from APL

transpose: ⍉

This method transposes a matrix row/column.


(rho 3 4 (iota 5)) ; is ((1 2 3 4) (5 1 2 3) (4 5 1 2))
(transpose (rho 3 4 (iota 5))) ; yields ((1 5 4) (2 1 5) (3 2 1) (4 3 2))

; you can also use the operator: ⍉
(⍉ (rho 3 4 (iota 5))) ; yields ((1 5 4) (2 1 5) (3 2 1) (4 3 2))

scan: \\

  • (\\ 'op list (slice)) : applies a function to sublists of slice elements. Default value for slice is 2.
  • (\\ (λ(acc x) ..) list (slice)) : applies a lambda expression to the list with acc as an accumulator
  • (\\ 'integers list) either extend nb times the corresponding element. When the value in integers is 0 add 0 to the list.

slice is an optional parameter that can be used to apply the operator to sublists of size slice

; apply an operator

(\\ '+ '(1 2 3)) ; yields (1 3 6) [1 (1+2) ((1+2)+3)
(\\ '(2 0 3) '(45 26)) ; yields (45 45 0 26 26 26)

(\\ (λ(acc x) (- acc x)) '(1 2 3)) ; yields (1 -1 -4)

; You can also use the actual name

(scan '+ '(1 2 3)) ; yields (1 3 6) [1 (1+2) ((1+2)+3)

backscan: ⍀ or -\\

  • (-\\ 'op list (slice)) : applies a function to sublists of slice elements from the end. Default value for slice is 2.
  • (-\\ (λ(acc x)..) list (slice)) : applies a lambda-expression to the list
  • (-\\ 'boolean_list list) either keep the corresponding element or add a zero from the end

slice is an optional parameter that can be used to apply the operator to sublists of size slice

; apply an operator

(-\\ '- '(1 2 3)) ; yields (3 1 0)
(-\\ (λ(acc x) (- acc x)) '(1 2 3)) ; yields (3 1 0) as well

; You can also use the actual name

(backscan '- '(1 2 3)) ; yields (3 1 0)
; or 

(⍀ '- '(1 2 3)) ; yields (3 1 0)

reduce: //

  • (// list) : duplicates the list
  • (// 'op list (slice)): applies a function to sublists of slice elements. Default value for slice is 2.
  • (// (λ(acc x) ..) list (slice) : applies a lambda expression to the list with acc as an accumulator
  • (// integers list): extend the list according to the list of integers

slice is an optional parameter that can be used to apply the operator to sublists of size slice


(// '+ '(1 2 3)) ; yields 6
(// '(1 0 2) '(1 2 3)) ; yields (1 3 3)

(// (λ(acc x)  (- x acc)) (iota 10)) ; yields 5

; You can also use the alphabetical name
(reduce '+ '(1 2 3)) ; yields 6

backreduce: ⌿ or -//

  • (-// list) : reverse the list
  • (-// 'op list (slice)): applies a function to sublists of slice elements from the end. Default value for slice is 2.
  • (-// (λ(acc x) ..) list (slice)) : applies a lambda expression to the list with acc as an accumulator
  • (-// boolean_list list): keeps only the element for which a 1 is provided, from the end

slice is an optional parameter that can be used to apply the operator to sublists of size slice


(// '- '(1 2 3)) ; yields -4 : 1-2-3
(-// '- '(1 2 3)) ; yields 0 : 3-2-1

(-// (λ(acc x)  (- x acc)) (iota 10)) ; yields -5

; You can also use the alphabetical name
(backreduce '+ '(1 2 3)) ; yields 6

; or

(⌿ + '(1 2 3)) 

iota, iota0 (⍳, ⍳0): list of consecutive numerical values

  • (iota n) :returns a list of numerical values starting at 1 up to n included
  • (iota0 n) :returns a list of numerical values starting at 0 up to n excluded

You can also put more than one n in iota:

iota n n' n"...: returns a list of nb lists of n, n' and n'' elements

Note that if your seed value is a decimal value, then the list will be a list of numbers, otherwise it will be a list of integers.


(iota 10) ; yields (1 2 3 4 5 6 7 8 9 10)
(iota0 10) ; yields (0 1 2 3 4 5 6 7 8 9)

; If your value seed value is a decimal number, then the list will be a list of numbers
(iota 10.1) ; yields (1.1 2.1 3.1 4.1 5.1 6.1 7.1 8.1 9.1 10.1)
(iota0 10.1) ; yields (0.1 1.1 2.1 3.1 4.1 5.1 6.1 7.1 8.1 9.1)

; We can also use the corresponding Greek letter
(⍳ 3 4) ; yields ( (1 2 3) (1 2 3 4) )

rho (⍴): size

rho can be used in three different ways:

  • (rho list): it returns the size of the list. If the list is a matrix, it returns its two dimensions
  • (rho sz list): builds a list of size sz out of list
  • (rho sz1 sz2 list): builds a matrix of dimension sz1,sz2 out of list

(rho (iota 2 2)) ; yields (2 2)
(rho 10 (iota 3)) ; yields (1 2 3 1 2 3 1 2 3 1)

; We can also use the corresponding Greek letter
(⍴ 3 3 (iota 4)) ; yields ((1 2 3) (4 1 2) (3 4 1))


; rho can also be used to create a linked list. 
; If the input list is a linked list
; The list is then built backwards
(rho 2 3 (llist 1 2 3)); ((3 2 1) (3 2 1))

; The same for lists
(rho 2 3 (list 1 2 3)); ((1 2 3) (1 2 3))  

° : outer product

This involves two data items and a function. The function can be any dyadic function, including user-defined functions. The function operates on pairs of elements, one taken from the left argument and one from the right, till every possible combination of two elements has been used.

(° '* '(2 3 4)  '(1 2 3 4))
; Multiplies every number in X by every
; number in Y generating a multiplication
; table

((2 4 6 8) (3 6 9 12) (4 8 12 16))

.: inner product

Inner product takes the form:

      (. FN1 FN2 DATA1 DATA2)

Where the operands, FN1 and FN2, are both dyadic functions, including user-defined functions. Inner product first combines the data along the last axis of the left argument with the data along the first axis of the right argument in an 'Outer Product' operation with the right operand. Finally a 'reduction' operation is applied to each element of the result.

If the two arguments are vectors of the same size, then the inner product gives the same result as FN2 being applied to the data and then FN1 being applied to the result in a reduction operation.

(. '+ '* '(1 2 3) '(4 5 6)); yields 32

When applied to data of more than one dimension, such as matrices, the operation is more complex. For matrix arguments the shape of the result of the operation is given by deleting the two inner axes and joining the others in order. For example if we have:

         TABA of 4 rows and  columns

and TABB of 5 rows and 6 columns The inner dimensions are used by the inner product operation, and the result will be a 4-row 6-column matrix.

The operations take place between the rows and columns of the two matrices and are therefore the same as inner product operations between vectors as described above.

             TABLE1                         TABLE2
             1    2                         6  2  3  4
             5    4                         7  0  1  8
             3    0

Which we can write with the inner product as:


(.  '+ '* '((1 2) (5 4) (3 0)) '((6 2 3 4) (7 0 1 8)))

; which yields:

((20 2 5 20) (58 10 19 52) (18 6 9 12))

== : numerical Boolean

This operator returns either 0 or 1 for a comparison between two values. If the comparison is done with lists, then it returns a list with 1 and 0 where values are the same.

Note that if you use the operator = in a à la APL instruction, it will be replaced with ==.


(== '(1 2 3) '(1 5 3)) ; yields (1 0 1) 

; Here the = is automatically replaced with == in order to return numerical Boolean value
(° (iota 3) '= (iota 3)) ; yields ((1 0 0) (0 1 0) (0 0 1))

, : concatenate operator

  • As a monadic operator, it actually works as flatten.

  • As a dyadic operator, it concatenates lists together

(, (iota 3 3)) ; yields (1 2 3 1 2 3)

(, (iota 4) 5) ; yields ((1 5) (2 5) (3 5) (4 5))

(, (iota 2 2) (iota 2 2)) ; yields ((1 2 1 2) (1 2 1 2))

∈: (member m values)

This operator scans a container for elements that are part of the values list. It then returns a container, in which the elements that are part of values are set to 1 or 0 else.

(setq m (rho 3 3 (iota 9))) ; m is ((1 2 3) (4 5 6) (7 8 9))

(setq r (∈ m '(1 3 4))) ; r is ((1 0 1) (1 0 0) (0 0 0))

⍤: (rank m D1 D2...)

This operator applies to matrices and tensors. It returns a sub-matrix corresponding to the provided coordinates. Replacing a dimension with -1 skips it in the final matrix or in the final tensor. In this case, LispE returns a specific axe from the tensor object.

(setq m (rho 3 4 5 (iota 60)))
;(((1 2 3 4 5) (6 7 8 9 10) (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)) 
; ((41 42 43 44 45) (46 47 48 49 50) (51 52 53 54 55) (56 57 58 59 60)))


(rank m 1) ; yields ((21 22 23 24 25) (26 27 28 29 30) (31 32 33 34 35) (36 37 38 39 40))
(rank m 1 1) ; (26 27 28 29 30)
(rank m -1 1) ; yields ((6 7 8 9 10) (26 27 28 29 30) (46 47 48 49 50))
(rank m -1 -1 1) ; yields (2 22 42 7 27 47 12 32 52 17 37 57)

(irank D1 D2...)

This function gives the same result as rank. It should be used to loop across different dimensions

(setq m (rho 3 4 5 (iota 60)))

(loop d (irank m -1 -1) (print d))
; Displays:
;((1 6 11 16) (21 26 31 36) (41 46 51 56))
;((2 7 12 17) (22 27 32 37) (42 47 52 57))
;((3 8 13 18) (23 28 33 38) (43 48 53 58))
;((4 9 14 19) (24 29 34 39) (44 49 54 59))
;((5 10 15 20) (25 30 35 40) (45 50 55 60))

(determinant m)

Computes the matrix determinant of a square matrix m

(ludcmp m)

ludcmp applies to a matrix and returns a list of indexes. ludcmp replaces a real n-by-n matrix, m, with the LU decomposition of a row-wise permutation of itself. Important: the matrix is modified by the process.

(setq m (rho 2 2 (iota 4)))
(setq idx (ludcmp m)) 
; idx is (1 1) and m is now: ((3 4) (0.333333 0.666667))

(lubksb m idx y)

Solves the set of n linear equations mx = y. (lubksb must be used with the procedure ludcmp to do this.) y is optional. When y is not provided, then by default it will be the identity matrix, and it will act as a matrix inversion.

(setq m (rho 2 2 (iota 4))) 
(setq idx (ludcmp m)) ; m is modified
(setq y (lubksb m idx)) ; here the matrix is inverted

(setq m (matrix '((1 9 8) (2 3 4) (12 21 34))))
(setq y (matrix '((1 2 3) (4 5 6) (7 8 9))))
(setq idx (ludcmp m))
(setq x (lubksb m idx y)) ; here we solve the linear equations

⌹: (invert m), (solve w y)

  • invert provides the inversion of a matrix.
  • solve provides a way to solve linear equations such as: W×X = Y. It returns X. "×" is the matrix multiplication.

The inversion and the solver are based on a LUDCMP decomposition followed with a LUBKSB decomposition. Basically, inverting a matrix is to solve linear equations where Y is the identity matrix.

(invert (rho 2 2 (iota 4))) ; yields ((-2 1) (1.5 -0.5))

(setq w (matrix '((1 9 8) (2 3 4) (12 21 34))))
(setq y (matrix '((1 2 3) (4 5 6) (7 8 9))))
 
(setq x (solve  w y))
; yields x is ((3.94737 4.89474 5.84211) (1.61404 2.22807 2.84211) (-2.18421 -2.86842 -3.55263))
(. w + * x) ; yields ((1 2 3) (4 5 6) (7 8 9))