; FILE "general-table.scm"
; IMLEMENTS simple parameterized hash tables
; AUTHOR Ken Dickey
; DATE 1994 April 21
; LAST UPDATE 1994 April 22
; NOTES OO style (requires YASOS)
; lightly tested. Bugs & fixes to KenD@Newton.Apple.com
;(require 'yasos)
;; INTERFACE:
; (make-table) ->
keyed on objects
; (make-string-table) -> keyed on strings
; (make-symbol-table) -> keyed on symbols
; (table? obj) -> #t or #f
; (associate! table key value) ->
; (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 (lookup foo-table my-key fail)) )
; (if (eq? probe fail) )
;
; (remove! table key) -> ; removes key,value pair
; An error is signalled if key is not present
; (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
;-------------------------
;; HASH TABLE 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.
;; you may need the following: when unless while add1 sub1
;(define-syntax WHEN
; (syntax-rules ()
; ((when ...)
; ;; =>
; (if (begin ...) #f))
;) )
;(define-syntax UNLESS
; (syntax-rules ()
; ((unless ...)
; ;; =>
; (if #f (begin ...)))
;) )
;(define-syntax WHILE
; (syntax-rules ()
; ((while ...)
; ;; =>
; (let ( (test (lambda () )) )
; (let recur ( (c (test)) )
; (when c
; ...
; (recur (test)))
; ) )
;) ) )
;(define (ADD1 n) (+ n 1))
;(define (SUB1 n) (- n 1))
;; TABLE FUNCTIONS (def'ed in collect.oo -- uncomment if not loaded)
;(define-predicate TABLE?)
;(define-operation (LOOKUP table key failure-object))
;(define-operation (ASSOCIATE! table key value)) ;; returns key
;(define-operation (REMOVE! table key)) ;; returns value
(define-operation (WALK table proc)) ; not in collect.oo
(define (MAKE-GENERAL-TABLE-MAKER compare?-proc hash-proc)
;; TABLE RECORDS {locally scoped generic operations}
(define-predicate table-record?)
(define-operation (increment-size! self))
(define-operation (decrement-size! self))
(define-access-operation grow-size)
(define-operation (compare-fun self))
(define-operation (assoc-fun self))
(define-operation (hash-fun self))
(define-access-operation data)
(define-operation (num-buckets self))
(define-access-operation data-bucket)
(define-operation (zero! self))
(define (MAKE-TABLE-RECORD compare? assoc-fn hash-fn)
(let ( (num-pairs 0)
(grow-when 20)
(my-data (vector '()))
)
(object
((table-record? self) #t)
((size self) num-pairs)
((zero! self) (set! num-pairs 0))
((increment-size! self) (set! num-pairs (add1 num-pairs)))
((decrement-size! self) (set! num-pairs (sub1 num-pairs)))
((grow-size self) grow-when)
(((setter grow-size) self new-size)
(set! grow-when new-size))
((compare-fun self) compare?)
((assoc-fun self) assoc-fn)
((hash-fun self) hash-fn)
((num-buckets self) (vector-length my-data))
((data self) my-data)
(((setter data) self new-data) (set! my-data new-data))
((data-bucket self index) (vector-ref my-data index))
(((setter data-bucket) self index new-data)
(vector-set! my-data index new-data)
self)
) ) )
(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-record obj)
(let ( (number-of-buckets (num-buckets table-record)) )
(if (= 1 number-of-buckets)
0
(hash-fun obj number-of-buckets))
) ) )
(define MAX-TABLE-BUCKETS 10000) ; your milage may vary
(define (GROW-TABLE! table-obj table-record)
(let* ( (old-num-buckets (num-buckets table-record))
(new-num-buckets
(if (= old-num-buckets 1)
23
(+ 1 (* 3 old-num-buckets)))) ; should be relative prime
)
(set (grow-size table-record) (* new-num-buckets 10)) ;; N.B.: untuned
(when (< new-num-buckets max-table-buckets)
; rehash
(let ( (old-data (data table-record)) )
(set (data table-record) (make-vector new-num-buckets '()))
(zero! table-record) ; associate! adds to size
; walk old data
(let loop ( (index (sub1 (vector-length old-data))) )
(for-each (lambda (bucket)
(associate! table-obj (car bucket) (cdr bucket)))
(vector-ref old-data index))
(if (> index 0)
(loop (sub1 index))
table-obj)
) ) )
) )
(lambda () ; make-general-table-maker
(let ( (table
(make-table-record compare?-proc
(make-assoc compare?-proc)
(make-table-hash hash-proc)))
)
(object
;; table behaviors
((TABLE? self) #t)
((SIZE self) (size table))
((PRINT self port) (format port "#"))
((LOOKUP self key failure-marker)
(cond
(((assoc-fun table)
key
(data-bucket table ((hash-fun table) table key)))
=> cdr)
(else failure-marker))
)
((ASSOCIATE! self key value)
(let* ( (index ((hash-fun table) table key))
(alist (data-bucket table index))
)
(cond
(((assoc-fun table) key alist)
=> (lambda (bucket) (set-cdr! bucket value) key))
(else
(set (data-bucket table index) (cons (cons key value) alist))
(increment-size! table)
(if (> (size table) (grow-size table))
(grow-table! self table))
key))
) )
((REMOVE! self key) ;; returns old value
(let* ( (index ((hash-fun table) table key))
(alist (data-bucket table index))
(match? (compare-fun 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)) )
(set (data-bucket table index) (cdr alist))
(decrement-size! table)
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))
(decrement-size! table)
value)
)
(else
(loop (cdr last) (cdr this)))
) ) )
)) )
((WALK self proc)
(let loop ( (index (sub1 (num-buckets table))) )
(for-each proc (data-bucket table index))
(if (> index 0)
(loop (sub1 index))
'done))
)
;; collection behaviors
((COLLECTION? self) #t)
((GEN-KEYS self) ; lazy
(let* ( (bucket-index (sub1 (num-buckets table)))
(alist (data-bucket table bucket-index))
)
(lambda ()
(while (null? alist)
(when (zero? bucket-index)
(error "gen-keys: more keys asked for than available!"))
(set! bucket-index (sub1 bucket-index))
(set! alist (data-bucket table bucket-index)))
(let ( (key (caar alist)) )
(set! alist (cdr alist))
key)))
)
((GEN-ELTS self) ; lazy
(let* ( (bucket-index (sub1 (num-buckets table)))
(alist (data-bucket table bucket-index))
)
(lambda ()
(while (null? alist)
(when (zero? bucket-index)
(error "gen-keys: more keys asked for than available!"))
(set! bucket-index (sub1 bucket-index))
(set! alist (data-bucket table bucket-index)))
(let ( (elt (cdar alist)) )
(set! alist (cdr alist))
elt)))
)
((FOR-EACH-KEY self proc)
(walk table (lambda (bucket) (proc (car bucket))))
)
((FOR-EACH-ELT self proc)
(walk table (lambda (bucket) (proc (cdr bucket))))
)
) ) ) )
;-----------------------------------------------------------------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 (sub1 len)))
(if (>= i 0)
(let ((x (+ (* h 256) (char->integer (string-ref str i)))))
(loop (modulo x n) (sub1 i)))
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 ---