Search code examples
dictionarylispmultidimensional-arrayloops

Lisp: multidimensional array elementwise operations


What is the "correct" construct in Common Lisp to apply elementwise operations to multidimensional arrays?

The following examples should help illustrate what I'm trying to do:

A) Suppose I want to increase every element of an array by one:

0 1 2    1 2 3
3 4 5 -> 4 5 6
6 7 8    7 8 9

B) Suppose I want to add 2 arrays:

1 2   -1 -1    0 1
3 4 + -2 -2 -> 1 2
5 6   -3 -3    2 3

C) Suppose I want to find the largest elements of several arrays, elementwise:

max( 0 1 , 4 -1 , 0 0 ) -> 4 1
     2 3   0  0   8 1      8 3

Basically I think I'm looking for some sort of "arraymap" function which would be used in like so: (arraymap f A1 A2 ... An), where f takes n arguments as input, and the Ai are arrays of the same size.

In the above examples it would be used like so:

A)

(setq M #2A((0 1 2) (3 4 5) (6 7 8)))
(arraymap #'incf M)

B)

(setq M #2A((1 2) (3 4) (5 6)))
(setq N #2A((-1 -1) (-2 -2) (-3 -3)))
(arraymap #'+ M N)

C)

(setq M #2A((0 1) (2 3)))
(setq N #2A((4 -1) (0 0)))
(setq O #2A((0 0) (8 1)))
(arraymap #'max M N O)

I have tried some constructs with map and loop, but it seems to not work since multidimensional arrays are not a sequence type.


Solution

  • There are four ways to do that:

    • Write an ARRAY-MAP function based on the array dimensions and iterate over those.

    • Use ROW-MAJOR-AREF, which views the array like a vector.

    • Use displaced one-dimensional arrays for the operations.

    Example for a use of displaced arrays:

    (defun array-map (function &rest arrays)
      "maps the function over the arrays.
       Assumes that all arrays are of the same dimensions.
       Returns a new result array of the same dimension."
      (flet ((make-displaced-array (array)
               (make-array (reduce #'* (array-dimensions array))
                           :displaced-to array)))
        (let* ((displaced-arrays (mapcar #'make-displaced-array arrays))
               (result-array (make-array (array-dimensions (first arrays))))
               (displaced-result-array (make-displaced-array result-array)))
          (declare (dynamic-extent displaced-arrays displaced-result-array))
          (apply #'map-into displaced-result-array function displaced-arrays)
          result-array)))
    

    Using it:

    CL-USER 3 > (array-map #'1+ #2A((0 1 2) (3 4 5) (6 7 8)))
    
    #2A((1 2 3) (4 5 6) (7 8 9))
    
    CL-USER 4 > (array-map #'+ #2A((1 2) (3 4) (5 6)) #2A((-1 -1) (-2 -2) (-3 -3)) )
    
    #2A((0 1) (1 2) (2 3))
    
    • Use internal, implementation specific, operations for efficient array operations.