Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Branch: master
Fetching contributors…

Cannot retrieve contributors at this time

541 lines (480 sloc) 18.854 kB
;;; array arlib
;;; 2001 Jussi Piitulainen
;;; This is a high level implementation of some generally useful
;;; array procedures. In addition to R5RS and SRFI-25, only one
;;; tool is used, namely array:apply-to-vector and friends. Thus
;;; this library serves to prove that the primitives really are
;;; primitives. - A lower level implementation would access some
;;; implementation details to bypass redundant checking and such.
;;; Note that these procedures are not necessarily designed with
;;; full care. Think of them as examples of what can be done.
;;; Important tools are also missing, including scans and reduces
;;; and many thinks that I have not even heard of yet.
;;; (array-shape arr) (array-length arr dim) (array-size arr)
;;; (array-equal? arr1 arr2)
;;; (shape-for-each shp proc [ind])
;;; (array-for-each-index arr proc [ind])
;;; (tabulate-array shp proc) (tabulate-array! shp proc ind)
;;; (array-retabulate! arr shp proc [ind])
;;; (array-map [shp] proc arr0 arr1 ...)
;;; (array-map! arr [shp] proc arr0 arr1 ...)
;;; (array->vector arr) (array->list arr)
;;; (share-array/prefix arr k ...) (share-row arr k) (share-column arr k)
;;; (share-array/origin arr k ...) (share-array/origin arr ind)
;;; (array-append dim arr0 arr1 ...)
;;; (transpose arr dim ...)
;;; (share-nths arr dim n)
;;; Naming problem: should all those index-object using procedures
;;; bang? The main argument, like shape, is not mutated.
;;; (array-shape arr)
(define (array-shape arr)
(let ((r (array-rank arr)))
(let ((m (make-array (shape 0 r 0 2))))
(do ((d 0 (+ d 1)))
((= d r)
m)
(array-set! m d 0 (array-start arr d))
(array-set! m d 1 (array-end arr d))))))
;;; (array-length arr dim)
(define (array-length arr dim)
(- (array-end arr dim)
(array-start arr dim)))
;;; (array-size arr)
(define (array-size arr)
(let ((r (array-rank arr)))
(do ((k 0 (+ k 1))
(p 1 (* p (array-length arr k))))
((= k r) p))))
;;; (array-equal? a b)
;;; compares elements with equal? so elements better not contain
;;; arrays.
(define (array-equal? a b)
(let ((r (array-rank a)))
(and (= r (array-rank b))
(and (do ((k 0 (+ k 1))
(true #t (and true
(= (array-start a k)
(array-start b k))
(= (array-end a k)
(array-end b k)))))
((= k r) true))
(let ((ks (make-vector r 0)))
(let wok ((d 0))
(if (< d r)
(let ((e (array-end a d)))
(do ((k (array-start a d) (+ k 1))
(true #t (and true (wok (+ d 1)))))
((= k e) true)
(vector-set! ks d k)))
(equal? (array-ref a ks)
(array-ref b ks)))))))))
;;; (shape-for-each shp proc [index-object])
;;; passes each index in shape to proc in row-major orderd, using
;;; index-object if provided.
(define (shape-for-each shp proc . o)
(if (null? o)
(array:arlib:shape-for-each/arguments shp proc)
(if (vector? (car o))
(array:arlib:shape-for-each/vector shp proc (car o))
(array:arlib:shape-for-each/array shp proc (car o)))))
(define (array:arlib:shape-for-each/arguments shp proc)
(let ((r (array-end shp 0)))
(let ((vec (make-vector r)))
(let do-dim ((d 0))
(if (= d r)
(array:apply-to-vector r proc vec)
(let ((e (array-ref shp d 1)))
(do ((k (array-ref shp d 0) (+ k 1)))
((= k e))
(vector-set! vec d k)
(do-dim (+ d 1)))))))))
(define (array:arlib:shape-for-each/vector shp proc vec)
(let ((r (array-end shp 0)))
(let do-dim ((d 0))
(if (= d r)
(proc vec)
(let ((e (array-ref shp d 1)))
(do ((k (array-ref shp d 0) (+ k 1)))
((= k e))
(vector-set! vec d k)
(do-dim (+ d 1))))))))
(define (array:arlib:shape-for-each/array shp proc arr)
;; arr is not vector
(let ((r (array-end shp 0)))
(let do-dim ((d 0))
(if (= d r)
(proc arr)
(let ((e (array-ref shp d 1)))
(do ((k (array-ref shp d 0) (+ k 1)))
((= k e))
(array-set! arr d k)
(do-dim (+ d 1))))))))
;;; (array-for-each-index arr proc [ind])
;;; is equivalent to
;;;
;;; (shape-for-each-index (array-shape arr) proc [ind])
;;;
;;; but is implemented without allocation of the shape, to prove
;;; that it can be so implemented.
(define (array-for-each-index arr proc . o)
(if (null? o)
(array:arlib:array-for-each-index/arguments arr proc)
(if (vector? (car o))
(array:arlib:array-for-each-index/vector arr proc (car o))
(array:arlib:array-for-each-index/array arr proc (car o)))))
(define (array:arlib:array-for-each-index/arguments arr proc)
(let ((r (array-rank arr)))
(let ((vec (make-vector r))
(apply (array:applier-to-vector r)))
(let do-dim ((d 0))
(if (= d r)
(apply proc vec)
(let ((e (array-end arr d)))
(do ((k (array-start arr d) (+ k 1)))
((= k e))
(vector-set! vec d k)
(do-dim (+ d 1)))))))))
(define (array:arlib:array-for-each-index/vector arr proc ind)
;; ind is a vector
(let ((r (array-rank arr)))
(let do-dim ((d 0))
(if (= d r)
(proc ind)
(let ((e (array-end arr d)))
(do ((k (array-start arr d) (+ k 1)))
((= k e))
(vector-set! ind d k)
(do-dim (+ d 1))))))))
(define (array:arlib:array-for-each-index/array arr proc ind)
;; ind is an array but not a vector
(let ((r (array-rank arr)))
(let do-dim ((d 0))
(if (= d r)
(proc ind)
(let ((e (array-end arr d)))
(do ((k (array-start arr d) (+ k 1)))
((= k e))
(array-set! ind d k)
(do-dim (+ d 1))))))))
;;; (tabulate-array shp proc)
;;; (tabulate-array! shp proc ind)
;;; returns a newly allocated array of the given shape with initial
;;; contents at each index whatever proc returns given the indices.
;;; The latter procedure reuses ind for package of indices.
(define (tabulate-array shp proc)
(let ((arr (make-array shp)))
(array:arlib:shape-for-each/vector
shp
(let ((apply (array:applier-to-vector (array-end shp 0))))
(lambda (ix) (array-set! arr ix (apply proc ix))))
(make-vector (array-end shp 0)))
arr))
(define (tabulate-array! shp proc ind)
(let ((arr (make-array shp)))
(if (vector? ind)
(array:arlib:shape-for-each/vector
shp
(lambda (ix) (array-set! arr ix (proc ix)))
ind)
(array:arlib:shape-for-each/array
shp
(lambda (ix) (array-set! arr ix (proc ix)))
ind))
arr))
;;; (array-retabulate! arr shp proc [index-object])
;;; sets the elements of arr in shape to the value of proc at that
;;; index, using index-object if provided.
(define (array-retabulate! arr shp proc . o)
(if (null? o)
(array:arlib:shape-for-each/vector
shp
(let ((apply (array:applier-to-vector (array-end shp 0))))
(lambda (ix)
(array-set! arr ix (apply proc ix))))
(make-vector (array-end shp 0)))
(if (vector? (car o))
(array:arlib:shape-for-each/vector
shp
(lambda (ix)
(array-set! arr ix (proc ix)))
(car o))
(array:arlib:shape-for-each/array
shp
(lambda (ix)
(array-set! arr ix (proc ix)))
(car o)))))
;;; (array-map! array [shape] proc array0 array1...)
;;; stores to the elements of array (in shape) the values of proc at
;;; the contents of arrayk at corresponding index.
(define (array-map! arr x y . o)
(if (array:array? x)
(array:arlib:map! arr x y (apply vector o))
(array:arlib:map! arr (array-shape arr) x (apply vector y o))))
(define (array:arlib:map! arr shp proc args)
(let ((rank (vector-length args)))
(let ((argv (make-vector rank)))
(array:arlib:shape-for-each/vector
shp
(let ((apply (array:applier-to-vector rank)))
(lambda (ix)
(do ((k 0 (+ k 1)))
((= k rank))
(vector-set! argv k (array-ref (vector-ref args k) ix)))
(array-set! arr ix (apply proc argv))))
(make-vector (array-end shp 0))))))
;;; (array-map [shape] proc array0 array1 ...)
;;; creates a new array with elements initialized to the values of
;;; proc at contents of arrayk (in shape).
(define (array-map x y . o)
(if (array:array? x)
(let ((arr (make-array x)))
(array:arlib:map! arr x y (apply vector o))
arr)
(let ((shp (array-shape y)))
(let ((arr (make-array shp)))
(array:arlib:map! arr shp x (apply vector y o))
arr))))
;;; SRFI-25 mailing list requested array->vector; they also requested the
;;; ability to use an array as an index of an element, and array->list is
;;; an attempt to provide for that.
(define (array->vector arr)
(let ((vec (make-vector (array-size arr))))
(let ((k 0))
(shape-for-each
(array-shape arr)
(lambda (index)
(vector-set! vec k (array-ref arr index))
(set! k (+ k 1)))
(make-vector (array-rank arr)))
vec)))
;;; It needs to be said that more efficient implementations are
;;; possible, even within SRFI-25.
(define (array->list arr)
(vector->list (array->vector arr)))
;;; (share-row arr k)
;;; shares whatever the first index is about.
;;; The result has one dimension less.
(define (share-row arr k)
(share-array
arr
(let ((bounds (array->list (array-shape arr))))
(apply shape (cddr bounds)))
(lambda ks
(apply values k ks))))
;;; (share-array/prefix arr k ...)
(define (share-array/prefix arr . js)
(if (or (null? js)
(integer? (car js)))
(share-array
arr
(let ((bounds (array->list (array-shape arr))))
(apply shape (list-tail bounds (* 2 (length js)))))
(lambda ks
(apply values (append js ks))))
(apply (lambda (fix)
(share-array/prefix!
arr
fix
(make-vector (- (array-rank arr)
(if (vector? fix)
(vector-length fix)
(array-end fix 0))))))
js)))
(define (share-array/prefix! arr fix in . out)
(let* ((out (if (pair? out)
((lambda (out) out) out)
(make-vector (array-rank arr))))
(fix-ref (if (vector? fix) vector-ref array-ref))
(in-ref (if (vector? in) vector-ref array-ref))
(out-set! (if (vector? out) vector-set! array-set!))
(m (if (vector? fix)
(vector-length fix)
(array-end fix 0)))
(n (if (vector? out)
(vector-length out)
(array-end out 0))))
(do ((k 0 (+ k 1)))
((= k m))
(out-set! out k (fix-ref fix k)))
(share-array/index!
arr
(let ((bounds (array->list (array-shape arr))))
(apply shape (list-tail bounds (if (vector? fix)
(* 2 (vector-length fix))
(* 2 (array-end fix 0))))))
(lambda (in)
(do ((k m (+ k 1)))
((= k n))
(out-set! out k (in-ref in (- k m))))
out)
in)))
;;; (share-column arr k)
;;; shares whatever the second index is about.
;;; The result has one dimension less.
(define (share-column arr k)
(share-array
arr
(let ((bounds (array->list (array-shape arr))))
(apply shape
(car bounds) (cadr bounds)
(cddddr bounds)))
(lambda ks
(apply values (car ks) k (cdr ks)))))
;;; (share-array/origin arr k ...)
;;; (share-array/origin arr index)
;;; change origin to k ..., with index a vector or zero-based
;;; one-dimensional array that contains k ...
;;;
;;; This is useful for writing array-append. Maybe for something
;;; else too - who knows.
(define (share-array/origin arr . xs)
(let ((new (if (or (null? xs)
(integer? (car xs)))
xs
(apply (lambda (x)
(if (vector? x)
(vector->list x)
(if (array? x)
(array->list x)
(error "share-array/origin: bad thing"))))
xs))))
(do ((k (array-rank arr) (- k 1))
(old '() (cons (array-start arr (- k 1)) old)))
((= k 0)
(let ((ds (map - new old)))
(share-array
arr
(tabulate-array
(shape 0 (array-rank arr) 0 2)
(lambda (r k)
(case k
((0) (+ (array-start arr r) (list-ref ds r)))
((1) (+ (array-end arr r) (list-ref ds r))))))
(lambda ks
(apply values (map - ks ds)))))))))
;;; SRFI-25 mailing list requested making shapes their own type. Here's
;;; an example of how manipulating shapes as arrays can be useful. The
;;; example also tests that higher level libraries are indeed easy to
;;; write on top of this SRFI.
;;; (array-append arr1 arr2 dim)
;;; appends two arrays along a specified dimension. The arrays must
;;; have equally many dimensions and all other dimensions equally long.
;;;
;;; Generalize to more arrays and maybe rewrite with shape-for-each or
;;; what have you.
(define (array-append dim arr . ars)
(let* ((total (do ((m (array-length arr dim)
(+ m (array-length (car r) dim)))
(r ars (cdr r)))
((null? r) m)))
(common (array-shape arr))
(origin (array->vector (share-column common 0)))
(index (make-vector (array-rank arr))))
(array-set! common dim 1 (+ (array-start arr dim) total))
(let ((result (make-array common)))
(array-set! common dim 1 (array-start arr dim))
(let wok ((arr arr)
(ars ars))
(vector-set! origin dim (array-ref common dim 1))
(let ((arr1 (share-array/origin arr origin)))
(array-set! common dim 0 (array-start arr1 dim))
(array-set! common dim 1 (array-end arr1 dim))
(shape-for-each
common
(lambda (index)
(array-set! result index (array-ref arr1 index)))
index))
(if (pair? ars)
(wok (car ars) (cdr ars))))
result)))
;;; Transpose, as permutation of dimensions, is applicable to all
;;; arrays. The default is reversal.
;;; The implementation uses multiplication by permutation
;;; matrix but matrix multiplication is not exported.
(define (array:arlib:matrix-times a b)
(or (and (= (array-rank a) 2)
(= (array-rank b) 2))
(error "times: arrays are not matrices"))
(let ((r0 (array-start a 0)) (rn (array-end a 0))
(t0 (array-start a 1)) (tn (array-end a 1))
(u0 (array-start b 0)) (un (array-end b 0))
(k0 (array-start b 1)) (kn (array-end b 1)))
(or (= (- tn t0) (- un u0))
(error "times: matrices are not compatible"))
(let ((ab (make-array (shape r0 rn k0 kn))))
(do ((r r0 (+ r 1)))
((= r rn))
(do ((k k0 (+ k 1)))
((= k kn))
(do ((t t0 (+ t 1))
(u u0 (+ u 1))
(s 0 (+ s (* (array-ref a r t)
(array-ref b u k)))))
((and (= t tn)
(= u un))
(array-set! ab r k s)))))
ab)))
; This is a generalized transpose. It can permute the dimensions any which
; way. The permutation is provided by a permutation matrix: a square matrix
; of zeros and ones, with exactly one one in each row and column, or a
; permutation of the rows of an identity matrix; the size of the matrix
; must match the number of dimensions of the array.
;
; The default permutation is [ 0 1 | 1 0 ] of course, but any permutation
; array can be specified, and the shape array of the original array is then
; multiplied with it, and index column vectors of the new array with its
; inverse, from left, to permute the rows appropriately.
(define (array:arlib:permutation-matrix . ds)
(let* ((n (length ds))
(arr (make-array (shape 0 n 0 n) 0)))
(do ((k 0 (+ k 1))
(ds ds (cdr ds)))
((= k n))
(array-set! arr k (car ds) 1))
arr))
;;; (transpose arr k ...)
;;; shares arr with permuted dimensions. Each dimension from 0
;;; inclusive to rank exclusive must appear once in k ...
(define (transpose a . p0)
(let* ((r (array-rank a))
(permutation (apply array:arlib:permutation-matrix
(if (pair? p0)
p0
(do ((ds '() (cons d ds))
(d 0 (+ d 1)))
((= d r)
;; reverse dimensions
ds)))))
(inverse-permutation (share-array permutation
(array-shape permutation)
(lambda (r k)
;; transpose
(values k r)))))
(share-array
a
(array:arlib:matrix-times permutation (array-shape a))
(lambda ks0
(apply values
(array->list
(array:arlib:matrix-times
inverse-permutation
(apply array (shape 0 r 0 1) ks0))))))))
;;; (share-array/index! array subshape proc index)
(define (share-array/index! array subshape proc index)
(array:share/index! array subshape proc index))
;;; Take every nth slice along dimension d into a shared array. This
;;; preserves the origin.
(define (share-nths arr d n)
(let* ((bounds (array->vector (array-shape arr)))
(b (vector-ref bounds (* 2 d)))
(e (vector-ref bounds (+ (* 2 d) 1))))
(vector-set! bounds (+ (* 2 d) 1) (+ b (quotient (+ n (- e b 1)) n)))
(share-array
arr
(apply shape (vector->list bounds))
(lambda ks
(apply values
(let d/nk ((u 0) (ks ks))
(if (= u d)
(cons (+ b (* n (- (car ks) b))) (cdr ks))
(cons (car ks) (d/nk (+ u 1) (cdr ks))))))))))
Jump to Line
Something went wrong with that request. Please try again.