; FILE "general-table.scm" ; IMLEMENTS simple parameterized hash tables ; AUTHOR Ken Dickey ; DATE 1994 April 17 ; LAST UPDATE 1994 April 21 -- misc. cleanup ; NOTES Procedural version. Heavily influenced by JAReese and ; Kelsey's S48 version and YASOS collections. ; This is expository code in raw IEEE Scheme without macros or records. ; lightly tested. Bugs & fixes to KenD@Newton.Apple.com ;; INTERFACE: ; (make-table) -> keyed on objects ; (make-string-table) ->
keyed on strings ; (make-symbol-table) ->
keyed on symbols ; (table? obj) -> #t or #f ; (table-associate! table key value) -> ; (table-lookup table key failure-marker) -> or failure-marker ; Failure-marker allows for #f values. ; Eg: (let* ( (fail (cons #f #f)) ; not eq? to anything else ; (probe (table-lookup foo-table my-key fail)) ) ; (if (eq? probe fail) ) ; ; (table-remove! table key) -> ; removes key,value pair ; An error is signalled if key is not present ; (table-size table) -> ; Eg: (define (make-symbol-table) ; (make-general-table-maker eq? hash-symbol)) ; These can be put somewhere else: ; (hash-symbol sym n) -> 0..n-1 ; (hash-string str n) -> 0..n-1 ; (hash-number int n) -> 0..n-1 ; (hash-char char n) -> 0..n-1 ; (hash-vector vec n) -> 0..n-1 ; (hash-object obj n) -> 0..n-1 ;; DESIGN NOTES ; A table is a record, one of whose slots is a vector of alists. Each ; slot in the vector is a hash bucket. Initially there is only one ; bucket, so the table is just a fancy alist. As the table grows, the ; number of buckets is increased and the table rehashed. The growth ; criteria and hashing functions are fairly simple minded. Tables are ; vectors which are huge and ugly when printed. You probably want to use ; a true record or OO version of this code rather than this expository ; version. ;; TABLE FUNCTIONS (def'ed in inner scope) (define table? 'bogus) (define table-lookup 'bogus) (define table-associate! 'bogus) (define table-remove! 'bogus) (define table-size 'bogus) (define table-for-each 'bogus) (define (MAKE-GENERAL-TABLE-MAKER compare?-fun hash-fun) (define (MAKE-ASSOC compare-fn) ; memoize assoc-funs to save space (letrec ( (assoc-alist (list (cons eq? assq) (cons eqv? assv) (cons equal? assoc))) (really-make-assoc (lambda (match?) (lambda (key alist) (let loop ( (alist alist) ) (cond ((null? alist) #f) ((match? key (caar alist)) (car alist)) (else (loop (cdr alist)))))))) ) (cond ((assq compare-fn assoc-alist) => cdr) (else (set! assoc-alist (cons (cons compare-fn (really-make-assoc compare-fn)) assoc-alist)))) ) ) ; end make-assoc (define (MAKE-TABLE-HASH hash-fun) ; internalize hash-fun (lambda (table obj) (let ( (num-buckets (tr-num-buckets table)) ) (if (= 1 num-buckets) 0 (hash-fun obj num-buckets)) ) ) ) (define MAX-TABLE-BUCKETS 10000) ; your milage may vary (define (GROW-TABLE! table) (let* ( (old-num-buckets (tr-num-buckets table)) (new-num-buckets (if (= old-num-buckets 1) 23 (+ 1 (* 3 old-num-buckets)))) ; should be relative prime ) (tr-grow-size-set! table (* new-num-buckets 10)) ;; N.B.: untuned (if (< new-num-buckets max-table-buckets) ; rehash (let ( (old-data (tr-data table)) ) (tr-data-set! table (make-vector new-num-buckets '())) ; walk old data (let loop ( (index (- (vector-length old-data) 1)) ) (for-each (lambda (bucket) (associate! table (car bucket) (cdr bucket))) (vector-ref old-data index)) (if (> index 0) (loop (- index 1)) table) ) ) ) ) ) ;; TABLE "RECORDS" (define TR-MARKER "table" ) (define (TR? tr) (and (vector? tr) (= (vector-length tr) 7) (eq? tr-marker (vector-ref tr 0))) ) (define (MAKE-TABLE-RECORD compare? assoc-fn hash-fn) ; 0 1 2 3 4 5 6 (vector tr-marker 0 50 compare? assoc-fn hash-fn (vector '())) ) (define (TR-SIZE tr) (vector-ref tr 1) ) (define (TR-GROW-SIZE tr) (vector-ref tr 2) ) (define (TR-COMPARE tr) (vector-ref tr 3) ) (define (TR-ASSOC tr) (vector-ref tr 4) ) (define (TR-HASH tr) (vector-ref tr 5) ) (define (TR-DATA tr) (vector-ref tr 6) ; vector of buckets ) (define (TR-NUM-BUCKETS tr) (vector-length (tr-data tr)) ) (define (TR-SIZE-SET! tr new-size) (vector-set! tr 1 new-size) ) (define (TR-GROW-SIZE-SET! tr new-size) (vector-set! tr 2 new-size) ) (define (TR-DATA-SET! tr new-data) (vector-set! tr 6 new-data) ) (define (TR-DATA-BUCKET tr bucket-index) (vector-ref (tr-data tr) bucket-index) ; zero based ) (define (TR-DATA-BUCKET-SET! tr bucket-index new-alist) (vector-set! (tr-data tr) bucket-index new-alist) ) ;; EXPORTED FUNCTIONS (define (LOOKUP table key failure-marker) (cond (((tr-assoc table) key (tr-data-bucket table ((tr-hash table) table key))) => cdr) (else failure-marker)) ) (define (ASSOCIATE! table key value) (let* ( (index ((tr-hash table) table key)) (alist (tr-data-bucket table index)) ) (cond (((tr-assoc table) key alist) => (lambda (bucket) (set-cdr! bucket value) key)) (else (tr-data-bucket-set! table index (cons (cons key value) alist)) (tr-size-set! table (+ 1 (tr-size table))) (if (> (tr-size table) (tr-grow-size table)) (grow-table! table)) key)) ) ) (define (REMOVE! table key) ;; returns old value (let* ( (index ((tr-hash table) table key)) (alist (tr-data-bucket table index)) (match? (tr-compare table)) ) (cond ; empty alist? ((null? alist) (error "TABLE:REMOVE! Key not found: " key)) ; 1st bucket in alist? ((match? key (caar alist)) (let ( (value (cdar alist)) ) (tr-data-bucket-set! table index (cdr alist)) (tr-size-set! table (- (tr-size table) 1)) value) ) ; general case (else (let loop ( (last alist) (this (cdr alist)) ) (cond ((null? this) (error "TABLE:REMOVE! Key not found: " key)) ((match? key (caar this)) (let ( (value (cdar this)) ) (set-cdr! last (cdr this)) (tr-size-set! table (- (tr-size table) 1)) value) ) (else (loop (cdr last) (cdr this))) ) ) ) )) ) (define (WALK table proc) (let loop ( (index (- (tr-num-buckets tr) 1)) ) (for-each proc (tr-data-bucket table index)) (if (> index 0) (loop (- index 1)) 'done) ) ) (set! table-for-each walk) (set! table-associate! associate!) (set! table-remove! remove!) (set! table-size tr-size) (set! table-lookup lookup) (set! table? tr?) (lambda () ; make-general-table-maker (make-table-record compare?-fun (make-assoc compare?-fun) (make-table-hash hash-fun)) ) ) ;------------------------------------------------------------------v ; simple hash functions from "proplist.scm" (c) 1991, Marc Feeley (define (HASH-SYMBOL sym n) (hash-string (symbol->string sym) n)) (define (HASH-STRING str n) (let ((len (string-length str))) (let loop ((h 0) (i (- len 1))) (if (>= i 0) (let ((x (+ (* h 256) (char->integer (string-ref str i))))) (loop (modulo x n) (- i 1))) h)))) (define (HASH-NUMBER num n) (modulo (inexact->exact (floor (cond ((integer? num) num) ((rational? num) (+ (numerator num) (denominator num))) ((real? num) num) (else (+ (real-part num) (imag-part num)))))) n)) (define (HASH-CHAR chr n) (modulo (char->integer chr) n)) (define (HASH-VECTOR vec n) (modulo (vector-length vec) n)) (define (HASH-OBJECT obj n) (cond ((symbol? obj) (hash-symbol obj n)) ((string? obj) (hash-string obj n)) ((number? obj) (hash-number obj n)) ((char? obj) (hash-char obj n)) ((vector? obj) (hash-vector obj n)) ((pair? obj) 0) (else (modulo 1 n)))) ;------------------------------------------------------------------^ (define MAKE-TABLE (make-general-table-maker eq? hash-object)) (define MAKE-STRING-TABLE (make-general-table-maker string=? hash-string)) (define MAKE-SYMBOL-TABLE (make-general-table-maker eq? hash-symbol)) ;; --- E O F ---