6.6.13.4 Arrays as arrays of arrays

One can see an array of rank n (an n-array) as an array of lower rank where the elements are themselves arrays (‘cells’).

We speak of the first n-k dimensions of the array as the n-k-‘frame’ of the array, while the last k dimensions are the dimensions of the k-‘cells’. For example, a 3-array can be seen as a 2-array of vectors (1-arrays) or as a 1-array of matrices (2-arrays). In each case, the vectors or matrices are the 1-cells or 2-cells of the array. This terminology originates in the J language.

The more vague concept of a ‘slice’ refers to a subset of the array where some indices are fixed and others are left free. As a Guile data object, a cell is the same as a ‘prefix slice’ (the first n-k indices into the original array are fixed), except that a 0-cell is not a shared array of the original array, but a 0-slice (where all the indices into the original array are fixed) is.

Before version 2.0, Guile had a feature called ‘enclosed arrays’ to create special ‘array of arrays’ objects. The functions in this section do not need special types; instead, the frame rank is stated in each function call, either implicitly or explicitly.

Scheme Procedure: array-cell-ref array idx …
C Function: scm_array_cell_ref (array, idxlist)

If the length of idxlist equals the rank n of array, return the element at (idx …), just like (array-ref array idx …). If, however, the length k of idxlist is smaller than n, then return the (n-k)-cell of array given by idxlist, as a shared array.

For example:

(array-cell-ref #2((a b) (c d)) 0) ⇒ #(a b)
(array-cell-ref #2((a b) (c d)) 1) ⇒ #(c d)
(array-cell-ref #2((a b) (c d)) 1 1) ⇒ d
(array-cell-ref #2((a b) (c d))) ⇒ #2((a b) (c d))

(apply array-cell-ref array indices) is equivalent to

(let ((len (length indices)))
  (if (= (array-rank a) len)
    (apply array-ref a indices)
    (apply make-shared-array a
           (lambda t (append indices t))
           (drop (array-dimensions a) len))))
Scheme Procedure: array-slice array idx …
C Function: scm_array_slice (array, idxlist)

Like (array-cell-ref array idx …), but return a 0-rank shared array into ARRAY if the length of idxlist matches the rank of array. This can be useful when using ARRAY as a place to write to.

Compare:

(array-cell-ref #2((a b) (c d)) 1 1) ⇒ d
(array-slice #2((a b) (c d)) 1 1) ⇒ #0(d)
(define a (make-array 'a 2 2))
(array-fill! (array-slice a 1 1) 'b)
a ⇒ #2((a a) (a b)).
(array-fill! (array-cell-ref a 1 1) 'b) ⇒ error: not an array

(apply array-slice array indices) is equivalent to

(apply make-shared-array a
  (lambda t (append indices t))
  (drop (array-dimensions a) (length indices)))
Scheme Procedure: array-cell-set! array x idx …
C Function: scm_array_cell_set_x (array, x, idxlist)

If the length of idxlist equals the rank n of array, set the element at (idx …) of array to x, just like (array-set! array x idx …). If, however, the length k of idxlist is smaller than n, then copy the (n-k)-rank array x into the (n-k)-cell of array given by idxlist. In this case, the last (n-k) dimensions of array and the dimensions of x must match exactly.

This function returns the modified array.

For example:

(array-cell-set! (make-array 'a 2 2) b 1 1)
  ⇒ #2((a a) (a b))
(array-cell-set! (make-array 'a 2 2) #(x y) 1)
  ⇒ #2((a a) (x y))

Note that array-cell-set! will expect elements, not arrays, when the destination has rank 0. Use array-slice for the opposite behavior.

(array-cell-set! (make-array 'a 2 2) #0(b) 1 1)
  ⇒ #2((a a) (a #0(b)))
(let ((a (make-array 'a 2 2)))
  (array-copy! #0(b) (array-slice a 1 1)) a)
  ⇒ #2((a a) (a b))

(apply array-cell-set! array x indices) is equivalent to

(let ((len (length indices)))
  (if (= (array-rank array) len)
    (apply array-set! array x indices)
    (array-copy! x (apply array-cell-ref array indices)))
  array)
Scheme Procedure: array-slice-for-each frame-rank op x …
C Function: scm_array_slice_for_each (array, frame_rank, op, xlist)

Each x must be an array of rank ≥ frame-rank, and the first frame-rank dimensions of each x must all be the same. array-slice-for-each calls op with each set of (rank(x) - frame-rank)-cells from x, in unspecified order.

array-slice-for-each allows you to loop over cells of any rank without having to carry an index list or construct shared arrays manually. The slices passed to op are always shared arrays of X, even if they are of rank 0, so it is possible to write to them.

This function returns an unspecified value.

For example, to sort the rows of rank-2 array a:

(array-slice-for-each 1 (lambda (x) (sort! x <)) a)

As another example, let a be a rank-2 array where each row is a 2-element vector (x,y). Let’s compute the arguments of these vectors and store them in rank-1 array b.

(array-slice-for-each 1
  (lambda (a b)
    (array-set! b (atan (array-ref a 1) (array-ref a 0))))
  a b)

(apply array-slice-for-each frame-rank op x) is equivalent to

(let ((frame (take (array-dimensions (car x)) frank)))
  (unless (every (lambda (x)
                   (equal? frame (take (array-dimensions x) frank)))
                 (cdr x))
    (error))
  (array-index-map!
    (apply make-shared-array (make-array #t) (const '()) frame)
    (lambda i (apply op (map (lambda (x) (apply array-slice x i)) x)))))
Scheme Procedure: array-slice-for-each-in-order frame-rank op x …
C Function: scm_array_slice_for_each_in_order (array, frame_rank, op, xlist)

Same as array-slice-for-each, but the arguments are traversed sequentially and in row-major order.