; Copyright (c) 1993-1999 by Richard Kelsey and Jonathan Rees. See file COPYING. ; Hash table package that allows for different hash and comparison functions. ; ; The fields in a table are: ; size - the number of entries ; data - an a-list or vector of a-lists ; ref - a procedure: [table index] -> value ; set - a procedure: [table index new-value] -> void ; ; In small tables the data is stored in an a-list and no hashing is used. ; In large tables the data is stored in a vector of a-lists, with hashing ; used to index into the vector. When a small table grows large the REF ; and SET fields are replaced with vector-oriented versions. (define-record-type table :table (really-make-table size data ref set) table? (size table-size set-table-size!) (data table-data set-table-data!) (ref table-ref-procedure set-table-ref-procedure!) (set table-set!-procedure set-table-set!-procedure!)) (define (table-ref table key) ((table-ref-procedure table) table key)) (define (table-set! table key value) ((table-set!-procedure table) table key value)) ; This number is a guess (define linear-table-size-limit 15) (define (next-table-size count) ; Figure out next good size for table. (+ (* count 3) 1)) ; A table-maker is a thunk that returns a new, empty table each time it is ; called. There are four functions involved: ; assoc : [key alist] -> entry or #f ; ref-proc : [table key] -> entry or #f ; x->hash-table! : [assoc hash-function] -> void ; set!-proc : [table key value] -> void ; X->HASH-TABLE! replaces the data, ref, and set fields of the table, making ; it into a hash table. (define (make-table-maker comparison-function hash-function) (assoc->table-maker (make-assoc comparison-function) hash-function)) (define (assoc->table-maker assoc hash-function) (let* ((ref-proc (make-linear-table-ref assoc)) (x->hash-table! (make->hash-table assoc hash-function)) (set!-proc (make-linear-table-set! assoc x->hash-table!))) (lambda () (really-make-table 0 null-entry ref-proc set!-proc)))) ;---------------- ; A-lists. These are currently actual a-lists, because ASSQ is a VM primitive ; and thus very fast. (define null-entry '()) ; #f (define (new-entry key val others) ;(vector key val others) (cons (cons key val) others)) ; This abstraction is violated at times. (define entry-value cdr) (define entry-key car) (define set-entry-value! set-cdr!) ; ENTRIES is known to contain ENTRY. (define (delete-entry! entries entry) (if (eq? entry (car entries)) (cdr entries) (begin (let loop ((entries entries)) (if (eq? entry (cadr entries)) (set-cdr! entries (cddr entries)) (loop (cdr entries)))) entries))) (define (make-assoc pred) (if (eq? pred eq?) assq (lambda (thing alist) (let loop ((alist alist)) (cond ((null? alist) #f) ((pred thing (caar alist)) (car alist)) (else (loop (cdr alist)))))))) ; Using actual a-lists allows us to use ASSQ instead of the following. ;(define eq?-assoc ; (lambda (thing alist) ; (let loop ((alist alist)) ; (cond ((not alist) ; #f) ; ((eq? thing (vector-ref alist 0)) ; alist) ; (else ; (loop (vector-ref alist 2))))))) ;---------------- ; Turn some version of ASSOC into a table reference procedure for a-list ; tables. (define (make-linear-table-ref assoc) (lambda (table key) (let ((probe (assoc key (table-data table)))) (if probe (entry-value probe) #f)))) ; Turn some version of ASSOC and a hash function into a table set! procedure ; for a-list tables. If the table gets too big it is turned into a hash table. (define (make-linear-table-set! assoc x->hash-table!) (lambda (table key value) (let* ((data (table-data table)) (probe (assoc key data))) (cond (probe (if value (set-entry-value! probe value) (begin (set-table-data! table (delete-entry! data probe)) (set-table-size! table (- (table-size table) 1))))) (value (set-table-data! table (new-entry key value data)) (let ((size (table-size table))) (if (< size linear-table-size-limit) (set-table-size! table (+ size 1)) (x->hash-table! table size)))))))) ; Return a function to transform linear tables into hash tables. (define (make->hash-table assoc hash-function) (let ((hash-table-ref (make-hash-table-ref assoc hash-function)) (hash-table-set! (make-hash-table-set! assoc hash-function))) (lambda (table size) (let ((data (table-data table))) (set-table-ref-procedure! table hash-table-ref) (set-table-set!-procedure! table hash-table-set!) (table-expand-table! table (next-table-size size)) (table-enter-alist! table data))))) (define (make-hash-table-ref assoc hash-function) (lambda (table key) (let* ((data (table-data table)) (h (remainder (hash-function key) (vector-length data))) (alist (vector-ref data h)) (probe (assoc key alist))) (if probe (entry-value probe) #f)))) (define (make-hash-table-set! assoc hash-function) (lambda (table key value) (let* ((data (table-data table)) (h (remainder (hash-function key) (vector-length data))) (alist (vector-ref data h)) (probe (assoc key alist))) (cond (probe (if value (set-entry-value! probe value) (begin (vector-set! data h (delete-entry! alist probe)) (set-table-size! table (- (table-size table) 1))))) (value (vector-set! data h (new-entry key value alist)) (let ((size (+ (table-size table) 1))) (if (< size (vector-length data)) (set-table-size! table size) (expand-hash-table! table size)))))))) (define (expand-hash-table! table size) (let ((data (table-data table))) (table-expand-table! table (next-table-size size)) (do ((i 0 (+ i 1))) ((>= i (vector-length data))) (table-enter-alist! table (vector-ref data i))))) (define (table-enter-alist! table alist) (let ((set!-proc (table-set!-procedure table))) (do ((alist alist (cdr alist))) ((null? alist)) (set!-proc table (caar alist) (cdar alist))))) ; Reset the size and data of a table. The size will be incremented as ; the entries are added back into the table. (define (table-expand-table! table vector-size) (set-table-size! table 0) (set-table-data! table (make-vector vector-size null-entry))) (define (table-walk proc table) (really-table-walk (lambda (v) (proc (entry-key v) (entry-value v))) table)) (define (really-table-walk proc table) (let ((data (table-data table))) (cond ((null? data)) ((pair? data) (alist-walk proc data)) (else (do ((i 0 (+ i 1))) ((>= i (vector-length data))) (alist-walk proc (vector-ref data i))))))) (define (alist-walk proc alist) (do ((alist alist (cdr alist))) ((null? alist)) (proc (car alist)))) (define (make-table-immutable! table) (really-table-walk make-immutable! table) (make-immutable! (table-data table)) (make-immutable! table)) (define (table->entry-list table) (let ((list '())) (table-walk (lambda (k v) (set! list (cons v list))) table) list)) ; Actual tables ; The default hash function only on works on things that would work in ; a CASE expression. Even then, numbers don't really "work," since ; they are compared using eq?. (define (default-table-hash-function obj) (cond ((symbol? obj) (string-hash (symbol->string obj))) ((integer? obj) (if (< obj 0) (- -1 obj) obj)) ((char? obj) (+ 333 (char->integer obj))) ((eq? obj #f) 3001) ((eq? obj #t) 3003) ((null? obj) 3005) (else (error "value cannot be used as a table key" obj)))) (define eqv?-assoc (make-assoc eqv?)) (define (default-table-assoc key alist) (if (number? key) (eqv?-assoc key alist) (assq key alist))) ; (define string-hash (structure-ref features string-hash)) (define (symbol-hash symbol) (string-hash (symbol->string symbol))) (define make-table (let ((make-usual-table (assoc->table-maker default-table-assoc default-table-hash-function))) (lambda hash-function-option (if (null? hash-function-option) (make-usual-table) ((assoc->table-maker default-table-assoc (car hash-function-option))))))) (define make-string-table (make-table-maker string=? string-hash)) (define make-symbol-table (make-table-maker eq? symbol-hash)) (define make-integer-table (make-table-maker = abs))