; Copyright (c) 1993, 1994 Richard Kelsey and Jonathan Rees. See file COPYING. ; (make-array ...) ; (array-shape ) ; (array-ref ...) ; (array-set! ...) ; (make-shared-array ...) ; (copy-array ) ; (array->vector ) ; (array . ) ; ; All arrays are zero based. ; ; ARRAY-MAP returns a list containing the array's bounds. ; ; The argument to MAKE-SHARED-ARRAY is a linear function ; that maps indices into the shared array into a list of indices into ; the original array. The array returned by MAKE-SHARED-ARRAY shares ; storage with the original array. ; ; (array-ref (make-shared-array a f i1 i2 ... iN) j1 j2 ... jM) ; <==> ; (apply array-ref a (f j1 j2 ... jM)) ; ; ARRAY->VECTOR returns a vector containing the elements of an array ; in row-major order. ; An array consists of a vector containing the bounds of the array, ; a vector containing the elements of the array, and a linear map ; expressed as a vector of coefficients and one constant. ; If the map is #(c1 c2 ... cN C0) then the index into the vector of ; elements for (array-ref a i1 i2 ... iN) is ; (+ (* i1 c1) (* i2 c2) ... (* iN cN) C0). ; Interface due to Alan Bawden. ; Implementation by Richard Kelsey. (define-record-type array (bounds ; vector of array bounds map ; vector of coefficients + one constant elements) ; vector of actual elements ()) (define (array-shape array) (vector->list (array-bounds array))) ; Calculate the index into an array's element vector that corresponds to ; INDICES. MAP is the array's linear map. (define (fast-array-index indices map) (let ((size (- (vector-length map) 1))) (do ((i 0 (+ i 1)) (j (vector-ref map size) (+ j (* (vector-ref indices i) (vector-ref map i))))) ((>= i size) j)))) ; The same thing with bounds checking added. (define (array-index array indices) (let ((bounds (array-bounds array)) (coefficients (array-map array))) (let loop ((is indices) (i 0) (index (vector-ref coefficients (vector-length bounds)))) (cond ((null? is) (if (= i (vector-length bounds)) index (error "wrong number of array indices" array indices))) ((>= i (vector-length bounds)) (error "wrong number of array indices" array indices)) (else (let ((j (car is))) (if (and (>= j 0) (< j (vector-ref bounds i))) (loop (cdr is) (+ i 1) (+ index (* j (vector-ref coefficients i)))) (error "array index out of range" array indices)))))))) (define (array-ref array . indices) (vector-ref (array-elements array) (array-index array indices))) (define (array-set! array value . indices) (vector-set! (array-elements array) (array-index array indices) value)) ; This is mostly error checking. (define (make-array initial bound1 . bounds) (let* ((all-bounds (cons bound1 bounds)) (bounds (make-vector (length all-bounds))) (size (do ((bs all-bounds (cdr bs)) (i 0 (+ i 1)) (s 1 (* s (car bs)))) ((null? bs) s) (let ((b (car bs))) (vector-set! bounds i b) (if (not (and (integer? b) (exact? b) (< 0 b))) (error "illegal array bounds" all-bounds)))))) (array-maker bounds (bounds->map bounds) (make-vector size initial)))) (define (array bounds . elts) (let* ((array (apply make-array #f bounds)) (elements (array-elements array)) (size (vector-length elements))) (if (not (= (length elts) size)) (error "ARRAY got the wrong number of elements" bounds elts)) (do ((i 0 (+ i 1)) (elts elts (cdr elts))) ((null? elts)) (vector-set! elements i (car elts))) array)) ; Determine the linear map that corresponds to a simple array with the ; given bounds. (define (bounds->map bounds) (do ((i (- (vector-length bounds) 1) (- i 1)) (s 1 (* s (vector-ref bounds i))) (l '() (cons s l))) ((< i 0) (list->vector (reverse (cons 0 (reverse l))))))) ; This is mostly error checking. Two different procedures are used to ; check that the shared array does not extend past the original. The ; full check does a complete check, but, as it must check every corner ; of the shared array, it gets very slow as the number of dimensions ; goes up. The simple check just verifies that the all elements of ; the shared array map to elements in the vector of the original. (define (make-shared-array array linear-map . bounds) (let ((map (make-shared-array-map array linear-map bounds))) (if (if (<= (length bounds) maximum-full-bounds-check) (full-array-bounds-okay? linear-map bounds (array-bounds array)) (simple-array-bounds-okay? map bounds (vector-length (array-elements array)))) (array-maker (list->vector bounds) map (array-elements array)) (error "shared array out of bounds" array linear-map bounds)))) (define maximum-full-bounds-check 5) ; Check that every corner of the array specified by LINEAR and NEW-BOUNDS ; is within OLD-BOUNDS. This checks every corner of the new array. (define (full-array-bounds-okay? linear new-bounds old-bounds) (let ((old-bounds (vector->list old-bounds))) (let label ((bounds (reverse new-bounds)) (args '())) (if (null? bounds) (let loop ((res (apply linear args)) (bounds old-bounds)) (cond ((null? res) (null? bounds)) ((and (not (null? bounds)) (<= 0 (car res)) (< (car res) (car bounds))) (loop (cdr res) (cdr bounds))) (else #f))) (and (label (cdr bounds) (cons 0 args)) (label (cdr bounds) (cons (- (car bounds) 1) args))))))) ; Check that the maximum and minimum possible vector indices possible with ; the given bounds and map would fit in an array of the given size. (define (simple-array-bounds-okay? map bounds size) (do ((map (vector->list map) (cdr map)) (bounds bounds (cdr bounds)) (min 0 (if (> 0 (car map)) (+ min (* (car map) (- (car bounds) 1))) min)) (max 0 (if (< 0 (car map)) (+ max (* (car map) (- (car bounds) 1))) max))) ((null? bounds) (and (>= 0 (+ min (car map))) (< size (+ max (car map))))))) ; Determine the coefficients and constant of the composition of ; LINEAR-MAP and the linear map of ARRAY. BOUNDS is used only to ; determine the rank of LINEAR-MAP's domain. ; ; The coefficients are determined by applying first LINEAR-MAP and then ; ARRAY's map to the vectors (1 0 0 ... 0), (0 1 0 ... 0), ..., (0 ... 0 1). ; Applying them to (0 ... 0) gives the constant of the composition. (define (make-shared-array-map array linear-map bounds) (let* ((zero (map (lambda (ignore) 0) bounds)) (do-vector (lambda (v) (or (apply-map array (apply linear-map v)) (error "bad linear map for shared array" linear-map array bounds)))) (base (do-vector zero))) (let loop ((bs bounds) (ces '()) (unit (cons 1 (cdr zero)))) (if (null? bs) (list->vector (reverse (cons base ces))) (loop (cdr bs) (cons (- (do-vector unit) base) ces) (rotate unit)))))) ; Apply ARRAY's linear map to the indices in the list VALUES and ; return the resulting vector index. #F is returned if VALUES is not ; the correct length or if any of its elements are out of range. (define (apply-map array values) (let ((map (array-map array)) (bounds (array-bounds array))) (let loop ((values values) (i 0) (index (vector-ref map (vector-length bounds)))) (cond ((null? values) (if (= i (vector-length bounds)) index #f)) ((>= i (vector-length bounds)) #f) (else (let ((j (car values))) (if (and (>= j 0) (< j (vector-ref bounds i))) (loop (cdr values) (+ i 1) (+ index (* j (vector-ref map i)))) #f))))))) ; Return LIST with its last element moved to the front. (define (rotate list) (let ((l (reverse list))) (cons (car l) (reverse (cdr l))))) ; Copy an array, shrinking the vector if this is subarray that does not ; use all of the original array's elements. (define (copy-array array) (array-maker (array-bounds array) (bounds->map (array-bounds array)) (array->vector array))) ; Make a new vector and copy the elements into it. If ARRAY's map is ; the simple map for it's bounds, then the elements are already in the ; appropriate order and we can just copy the element vector. (define (array->vector array) (let* ((size (array-element-count array)) (new (make-vector size))) (if (and (= size (vector-length (array-elements array))) (equal? (array-map array) (bounds->map (array-bounds array)))) (copy-vector (array-elements array) new) (copy-elements array new)) new)) (define (array-element-count array) (let ((bounds (array-bounds array))) (do ((i 0 (+ i 1)) (s 1 (* s (vector-ref bounds i)))) ((>= i (vector-length bounds)) s)))) (define (copy-vector from to) (do ((i (- (vector-length to) 1) (- i 1))) ((< i 0)) (vector-set! to i (vector-ref from i)))) ; Copy the elements of ARRAY into the vector TO. The copying is done one ; row at a time. POSN is a vector containing the index of the row that ; we are currently copying. After the row is copied, POSN is updated so ; that the next row can be copied. A little more cleverness would make ; this faster by replacing the call to FAST-ARRAY-INDEX with some simple ; arithmetic on J. (define (copy-elements array to) (let ((bounds (array-bounds array)) (elements (array-elements array)) (map (array-map array))) (let* ((size (vector-length bounds)) (posn (make-vector size 0)) (step-size (vector-ref bounds (- size 1))) (delta (vector-ref map (- size 1)))) (let loop ((i 0)) (do ((i2 i (+ i2 1)) (j (fast-array-index posn map) (+ j delta))) ((>= i2 (+ i step-size))) (vector-set! to i2 (vector-ref elements j))) (cond ((< (+ i step-size) (vector-length to)) (let loop2 ((k (- size 2))) (cond ((= (+ (vector-ref posn k) 1) (vector-ref bounds k)) (vector-set! posn k 0) (loop2 (- k 1))) (else (vector-set! posn k (+ 1 (vector-ref posn k)))))) (loop (+ i step-size)))))))) ; Testing. ; (define a1 (make-array 0 4 5)) ; 0 1 2 3 ; 4 5 6 7 ; 8 9 10 11 ; 12 13 14 15 ; 16 17 18 19 ; (make-shared-array-map a1 (lambda (x) (list x x)) '(3)) ; 0 5 10, #(5 0) ; (make-shared-array-map a1 (lambda (x) (list 2 (- 4 x))) '(3)) ; 18 14 10 #(-4 18) ; (make-shared-array-map a1 (lambda (x y) (list (+ x 1) y)) '(2 4)) ; 1 2 ; 5 6 ; 9 10 ; 13 14 ; #(1 4 1)