123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337 |
- ;;; srfi-69.scm --- Basic hash tables
- ;; Copyright (C) 2007 Free Software Foundation, Inc.
- ;;
- ;; This library is free software; you can redistribute it and/or
- ;; modify it under the terms of the GNU Lesser General Public
- ;; License as published by the Free Software Foundation; either
- ;; version 3 of the License, or (at your option) any later version.
- ;;
- ;; This library is distributed in the hope that it will be useful,
- ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
- ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- ;; Lesser General Public License for more details.
- ;;
- ;; You should have received a copy of the GNU Lesser General Public
- ;; License along with this library; if not, write to the Free Software
- ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- ;;;; Commentary:
- ;; My `hash' is compatible with core `hash', so I replace it.
- ;; However, my `hash-table?' and `make-hash-table' are different, so
- ;; importing this module will warn about them. If you don't rename my
- ;; imports, you shouldn't use both my hash tables and Guile's hash
- ;; tables in the same module.
- ;;
- ;; SRFI-13 `string-hash' and `string-hash-ci' have more arguments, but
- ;; are compatible with my `string-hash' and `string-ci-hash', and are
- ;; furthermore primitive in Guile, so I use them as my own.
- ;;
- ;; I also have the extension of allowing hash functions that require a
- ;; second argument to be used as the `hash-table-hash-function', and use
- ;; these in defaults to avoid an indirection in the hashx functions. The
- ;; only deviation this causes is:
- ;;
- ;; ((hash-table-hash-function (make-hash-table)) obj)
- ;; error> Wrong number of arguments to #<primitive-procedure hash>
- ;;
- ;; I don't think that SRFI 69 actually specifies that I *can't* do this,
- ;; because it only implies the signature of a hash function by way of the
- ;; named, exported hash functions. However, if this matters enough I can
- ;; add a private derivation of hash-function to the srfi-69:hash-table
- ;; record type, like associator is to equivalence-function.
- ;;
- ;; Also, outside of the issue of how weak keys and values are referenced
- ;; outside the table, I always interpret key equivalence to be that of
- ;; the `hash-table-equivalence-function'. For example, given the
- ;; requirement that `alist->hash-table' give earlier associations
- ;; priority, what should these answer?
- ;;
- ;; (hash-table-keys
- ;; (alist->hash-table '(("xY" . 1) ("Xy" . 2)) string-ci=?))
- ;;
- ;; (let ((ht (make-hash-table string-ci=?)))
- ;; (hash-table-set! ht "xY" 2)
- ;; (hash-table-set! ht "Xy" 1)
- ;; (hash-table-keys ht))
- ;;
- ;; My interpretation is that they can answer either ("Xy") or ("xY"),
- ;; where `hash-table-values' will of course always answer (1), because
- ;; the keys are the same according to the equivalence function. In this
- ;; implementation, both answer ("xY"). However, I don't guarantee that
- ;; this won't change in the future.
- ;;; Code:
- ;;;; Module definition & exports
- (define-module (srfi srfi-69)
- #:use-module (srfi srfi-1) ;alist-cons,second&c,assoc
- #:use-module (srfi srfi-9)
- #:use-module (srfi srfi-13) ;string-hash,string-hash-ci
- #:use-module (ice-9 optargs)
- #:export (;; Type constructors & predicate
- make-hash-table hash-table? alist->hash-table
- ;; Reflective queries
- hash-table-equivalence-function hash-table-hash-function
- ;; Dealing with single elements
- hash-table-ref hash-table-ref/default hash-table-set!
- hash-table-delete! hash-table-exists? hash-table-update!
- hash-table-update!/default
- ;; Dealing with the whole contents
- hash-table-size hash-table-keys hash-table-values
- hash-table-walk hash-table-fold hash-table->alist
- hash-table-copy hash-table-merge!
- ;; Hashing
- string-ci-hash hash-by-identity)
- #:re-export (string-hash)
- #:replace (hash make-hash-table hash-table?))
- (cond-expand-provide (current-module) '(srfi-69))
- ;;;; Internal helper macros
- ;; Define these first, so the compiler will pick them up.
- ;; I am a macro only for efficiency, to avoid varargs/apply.
- (define-macro (hashx-invoke hashx-proc ht-var . args)
- "Invoke HASHX-PROC, a `hashx-*' procedure taking a hash-function,
- assoc-function, and the hash-table as first args."
- `(,hashx-proc (hash-table-hash-function ,ht-var)
- (ht-associator ,ht-var)
- (ht-real-table ,ht-var)
- . ,args))
- (define-macro (with-hashx-values bindings ht-var . body-forms)
- "Bind BINDINGS to the hash-function, associator, and real-table of
- HT-VAR, while evaluating BODY-FORMS."
- `(let ((,(first bindings) (hash-table-hash-function ,ht-var))
- (,(second bindings) (ht-associator ,ht-var))
- (,(third bindings) (ht-real-table ,ht-var)))
- . ,body-forms))
- ;;;; Hashing
- ;;; The largest fixnum is in `most-positive-fixnum' in module (guile),
- ;;; though not documented anywhere but libguile/numbers.c.
- (define (caller-with-default-size hash-fn)
- "Answer a function that makes `most-positive-fixnum' the default
- second argument to HASH-FN, a 2-arg procedure."
- (lambda* (obj #:optional (size most-positive-fixnum))
- (hash-fn obj size)))
- (define hash (caller-with-default-size (@ (guile) hash)))
- (define string-ci-hash string-hash-ci)
- (define hash-by-identity (caller-with-default-size hashq))
- ;;;; Reflective queries, construction, predicate
- (define-record-type srfi-69:hash-table
- (make-srfi-69-hash-table real-table associator size weakness
- equivalence-function hash-function)
- hash-table?
- (real-table ht-real-table)
- (associator ht-associator)
- ;; required for O(1) by SRFI-69. It really makes a mess of things,
- ;; and I'd like to compute it in O(n) and memoize it because it
- ;; doesn't seem terribly useful, but SRFI-69 is final.
- (size ht-size ht-size!)
- ;; required for `hash-table-copy'
- (weakness ht-weakness)
- ;; used only to implement hash-table-equivalence-function; I don't
- ;; use it internally other than for `ht-associator'.
- (equivalence-function hash-table-equivalence-function)
- (hash-function hash-table-hash-function))
- (define (guess-hash-function equal-proc)
- "Guess a hash function for EQUAL-PROC, falling back on `hash', as
- specified in SRFI-69 for `make-hash-table'."
- (cond ((eq? equal? equal-proc) (@ (guile) hash)) ;shortcut most common case
- ((eq? eq? equal-proc) hashq)
- ((eq? eqv? equal-proc) hashv)
- ((eq? string=? equal-proc) string-hash)
- ((eq? string-ci=? equal-proc) string-ci-hash)
- (else (@ (guile) hash))))
- (define (without-keyword-args rest-list)
- "Answer REST-LIST with all keywords removed along with items that
- follow them."
- (let lp ((acc '()) (rest-list rest-list))
- (cond ((null? rest-list) (reverse! acc))
- ((keyword? (first rest-list))
- (lp acc (cddr rest-list)))
- (else (lp (cons (first rest-list) acc) (cdr rest-list))))))
- (define (guile-ht-ctor weakness)
- "Answer the Guile HT constructor for the given WEAKNESS."
- (case weakness
- ((#f) (@ (guile) make-hash-table))
- ((key) make-weak-key-hash-table)
- ((value) make-weak-value-hash-table)
- ((key-or-value) make-doubly-weak-hash-table)
- (else (error "Invalid weak hash table type" weakness))))
- (define (equivalence-proc->associator equal-proc)
- "Answer an `assoc'-like procedure that compares the argument key to
- alist keys with EQUAL-PROC."
- (cond ((or (eq? equal? equal-proc)
- (eq? string=? equal-proc)) (@ (guile) assoc))
- ((eq? eq? equal-proc) assq)
- ((eq? eqv? equal-proc) assv)
- (else (lambda (item alist)
- (assoc item alist equal-proc)))))
- (define* (make-hash-table
- #:optional (equal-proc equal?)
- (hash-proc (guess-hash-function equal-proc))
- #:key (weak #f) #:rest guile-opts)
- "Answer a new hash table using EQUAL-PROC as the comparison
- function, and HASH-PROC as the hash function. See the reference
- manual for specifics, of which there are many."
- (make-srfi-69-hash-table
- (apply (guile-ht-ctor weak) (without-keyword-args guile-opts))
- (equivalence-proc->associator equal-proc)
- 0 weak equal-proc hash-proc))
- (define (alist->hash-table alist . mht-args)
- "Convert ALIST to a hash table created with MHT-ARGS."
- (let* ((result (apply make-hash-table mht-args))
- (size (ht-size result)))
- (with-hashx-values (hash-proc associator real-table) result
- (for-each (lambda (pair)
- (let ((handle (hashx-get-handle hash-proc associator
- real-table (car pair))))
- (cond ((not handle)
- (set! size (1+ size))
- (hashx-set! hash-proc associator real-table
- (car pair) (cdr pair))))))
- alist))
- (ht-size! result size)
- result))
- ;;;; Accessing table items
- ;; We use this to denote missing or unspecified values to avoid
- ;; possible collision with *unspecified*.
- (define ht-unspecified (cons *unspecified* "ht-value"))
- (define (hash-table-ref ht key . default-thunk-lst)
- "Lookup KEY in HT and answer the value, invoke DEFAULT-THUNK if KEY
- isn't present, or signal an error if DEFAULT-THUNK isn't provided."
- (let ((result (hashx-invoke hashx-ref ht key ht-unspecified)))
- (if (eq? ht-unspecified result)
- (if (pair? default-thunk-lst)
- ((first default-thunk-lst))
- (error "Key not in table" key ht))
- result)))
- (define (hash-table-ref/default ht key default)
- "Lookup KEY in HT and answer the value. Answer DEFAULT if KEY isn't
- present."
- (hashx-invoke hashx-ref ht key default))
- (define (hash-table-set! ht key new-value)
- "Set KEY to NEW-VALUE in HT."
- (let ((handle (hashx-invoke hashx-create-handle! ht key ht-unspecified)))
- (if (eq? ht-unspecified (cdr handle))
- (ht-size! ht (1+ (ht-size ht))))
- (set-cdr! handle new-value))
- *unspecified*)
- (define (hash-table-delete! ht key)
- "Remove KEY's association in HT."
- (with-hashx-values (h a real-ht) ht
- (if (hashx-get-handle h a real-ht key)
- (begin
- (ht-size! ht (1- (ht-size ht)))
- (hashx-remove! h a real-ht key))))
- *unspecified*)
- (define (hash-table-exists? ht key)
- "Return whether KEY is a key in HT."
- (and (hashx-invoke hashx-get-handle ht key) #t))
- ;;; `hashx-set!' duplicates the hash lookup, but we use it anyway to
- ;;; avoid creating a handle in case DEFAULT-THUNK exits
- ;;; `hash-table-update!' non-locally.
- (define (hash-table-update! ht key modifier . default-thunk-lst)
- "Modify HT's value at KEY by passing its value to MODIFIER and
- setting it to the result thereof. Invoke DEFAULT-THUNK for the old
- value if KEY isn't in HT, or signal an error if DEFAULT-THUNK is not
- provided."
- (with-hashx-values (hash-proc associator real-table) ht
- (let ((handle (hashx-get-handle hash-proc associator real-table key)))
- (cond (handle
- (set-cdr! handle (modifier (cdr handle))))
- (else
- (hashx-set! hash-proc associator real-table key
- (if (pair? default-thunk-lst)
- (modifier ((car default-thunk-lst)))
- (error "Key not in table" key ht)))
- (ht-size! ht (1+ (ht-size ht)))))))
- *unspecified*)
- (define (hash-table-update!/default ht key modifier default)
- "Modify HT's value at KEY by passing its old value, or DEFAULT if it
- doesn't have one, to MODIFIER, and setting it to the result thereof."
- (hash-table-update! ht key modifier (lambda () default)))
- ;;;; Accessing whole tables
- (define (hash-table-size ht)
- "Return the number of associations in HT. This is guaranteed O(1)
- for tables where #:weak was #f or not specified at creation time."
- (if (ht-weakness ht)
- (hash-table-fold ht (lambda (k v ans) (1+ ans)) 0)
- (ht-size ht)))
- (define (hash-table-keys ht)
- "Return a list of the keys in HT."
- (hash-table-fold ht (lambda (k v lst) (cons k lst)) '()))
- (define (hash-table-values ht)
- "Return a list of the values in HT."
- (hash-table-fold ht (lambda (k v lst) (cons v lst)) '()))
- (define (hash-table-walk ht proc)
- "Call PROC with each key and value as two arguments."
- (hash-table-fold ht (lambda (k v unspec)
- (call-with-values (lambda () (proc k v))
- (lambda vals unspec)))
- *unspecified*))
- (define (hash-table-fold ht f knil)
- "Invoke (F KEY VAL PREV) for each KEY and VAL in HT, where PREV is
- the result of the previous invocation, using KNIL as the first PREV.
- Answer the final F result."
- (hash-fold f knil (ht-real-table ht)))
- (define (hash-table->alist ht)
- "Return an alist for HT."
- (hash-table-fold ht alist-cons '()))
- (define (hash-table-copy ht)
- "Answer a copy of HT."
- (with-hashx-values (h a real-ht) ht
- (let* ((size (hash-table-size ht)) (weak (ht-weakness ht))
- (new-real-ht ((guile-ht-ctor weak) size)))
- (hash-fold (lambda (k v ign) (hashx-set! h a new-real-ht k v))
- #f real-ht)
- (make-srfi-69-hash-table ;real,assoc,size,weak,equiv,h
- new-real-ht a size weak
- (hash-table-equivalence-function ht) h))))
- (define (hash-table-merge! ht other-ht)
- "Add all key/value pairs from OTHER-HT to HT, overriding HT's
- mappings where present. Return HT."
- (hash-table-fold
- ht (lambda (k v ign) (hash-table-set! ht k v)) #f)
- ht)
- ;;; srfi-69.scm ends here
|