316 lines
10 KiB
Scheme
316 lines
10 KiB
Scheme
|
; Copyright (c) 1993, 1994 Richard Kelsey and Jonathan Rees. See file COPYING.
|
||
|
|
||
|
|
||
|
; (make-array <initial-value> <bound1> ...)
|
||
|
; (array-shape <array>)
|
||
|
; (array-ref <array> <index1> ...)
|
||
|
; (array-set! <array> <value> <index1> ...)
|
||
|
; (make-shared-array <array> <linear-map> <bound1> ...)
|
||
|
; (copy-array <array>)
|
||
|
; (array->vector <array>)
|
||
|
; (array <bounds> . <elements>)
|
||
|
;
|
||
|
; All arrays are zero based.
|
||
|
;
|
||
|
; ARRAY-MAP returns a list containing the array's bounds.
|
||
|
;
|
||
|
; The <linear-map> 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)
|
||
|
|