4.16 Dictionaries
A dictionary is an instance of a datatype that maps keys to values. The following datatypes are all dictionaries:
vectors (using only exact integers as keys);
lists of pairs as an association list using equal? to compare keys, which must be distinct; and
structures whose types implement the gen:dict generic interface.
When list of pairs is used as association list but does not have distinct keys (so it’s not an association list), operations like dict-ref and dict-remove operate on the first instance of the key, while operations like dict-map and dict-keys produce an element for every instance of the key.
(require racket/dict) | package: base |
4.16.1 Dictionary Predicates and Contracts
Beware that dict? is not a constant-time test on pairs, since checking that v is an association list may require traversing the list.
> (dict? #hash((a . "apple"))) #t
> (dict? '#("apple" "banana")) #t
> (dict? '("apple" "banana")) #f
> (dict? '((a . "apple") (b . "banana"))) #t
procedure
(dict-implements? d sym ...) → boolean?
d : dict? sym : symbol?
> (dict-implements? (hash 'a "apple") 'dict-set!) #f
> (dict-implements? (make-hash '((a . "apple") (b . "banana"))) 'dict-set!) #t
> (dict-implements? (make-hash '((b . "banana") (a . "apple"))) 'dict-remove!) #t
> (dict-implements? (vector "apple" "banana") 'dict-set!) #t
> (dict-implements? (vector 'a 'b) 'dict-remove!) #f
> (dict-implements? (vector 'a "apple") 'dict-set! 'dict-remove!) #f
procedure
(dict-implements/c sym ...) → flat-contract?
sym : symbol?
> (struct deformed-dict () #:methods gen:dict [])
> (define/contract good-dict (dict-implements/c) (deformed-dict))
> (define/contract bad-dict (dict-implements/c 'dict-ref) (deformed-dict)) bad-dict: broke its own contract
promised: (dict-implements/c dict-ref)
produced: #<deformed-dict>
in: (dict-implements/c dict-ref)
contract from: (definition bad-dict)
blaming: (definition bad-dict)
(assuming the contract is correct)
at: eval:14.0
procedure
(dict-mutable? d) → boolean?
d : dict?
Equivalent to (dict-implements? d 'dict-set!).
> (dict-mutable? #hash((a . "apple"))) #f
> (dict-mutable? (make-hash)) #t
> (dict-mutable? '#("apple" "banana")) #f
> (dict-mutable? (vector "apple" "banana")) #t
> (dict-mutable? '((a . "apple") (b . "banana"))) #f
procedure
d : dict?
Equivalent to (or (dict-implements? d 'dict-remove!) (dict-implements? d 'dict-remove)).
> (dict-can-remove-keys? #hash((a . "apple"))) #t
> (dict-can-remove-keys? '#("apple" "banana")) #f
> (dict-can-remove-keys? '((a . "apple") (b . "banana"))) #t
procedure
d : dict?
Equivalent to (dict-implements? d 'dict-set).
> (dict-can-functional-set? #hash((a . "apple"))) #t
> (dict-can-functional-set? (make-hash)) #f
> (dict-can-functional-set? '#("apple" "banana")) #f
> (dict-can-functional-set? '((a . "apple") (b . "banana"))) #t
4.16.2 Generic Dictionary Interface
syntax
> (struct alist (v) #:methods gen:dict [(define (dict-ref dict key [default (lambda () (error "key not found" key))]) (cond [(assoc key (alist-v dict)) => cdr] [else (if (procedure? default) (default) default)])) (define (dict-set dict key val) (alist (cons (cons key val) (alist-v dict)))) (define (dict-remove dict key) (define al (alist-v dict)) (alist (remove* (filter (λ (p) (equal? (car p) key)) al) al))) (define (dict-count dict) (length (remove-duplicates (alist-v dict) #:key car)))]) ; etc. other methods > (define d1 (alist '((1 . a) (2 . b)))) > (dict? d1) #t
> (dict-ref d1 1) 'a
> (dict-remove d1 1) #<alist>
value
dict-set!, or #f if unsupported
dict-set, or #f if unsupported
dict-remove!, or #f if unsupported
dict-remove, or #f if unsupported
4.16.2.1 Primitive Dictionary Methods
These methods of gen:dict have no fallback implementations; they are only supported for dictionary types that directly implement them.
procedure
dict : dict? key : any/c
failure-result : failure-result/c = (lambda () (raise (make-exn:fail ....)))
If failure-result is a procedure, it is called (through a tail call) with no arguments to produce the result.
Otherwise, failure-result is returned as the result.
> (dict-ref #hash((a . "apple") (b . "beer")) 'a) "apple"
> (dict-ref #hash((a . "apple") (b . "beer")) 'c) hash-ref: no value found for key
key: 'c
> (dict-ref #hash((a . "apple") (b . "beer")) 'c #f) #f
> (dict-ref '((a . "apple") (b . "banana")) 'b) "banana"
> (dict-ref #("apple" "banana") 1) "banana"
> (dict-ref #("apple" "banana") 3 #f) #f
> (dict-ref #("apple" "banana") -3 #f) dict-ref: contract violation
expected: natural?
given: -3
in: the k argument of
(->i
((d dict?) (k (d) (dict-key-contract d)))
((default any/c))
any)
contract from: <collects>/racket/dict.rkt
blaming: top-level
(assuming the contract is correct)
at: <collects>/racket/dict.rkt:182.2
> (define h (make-hash)) > (dict-set! h 'a "apple") > h '#hash((a . "apple"))
> (define v (vector #f #f #f)) > (dict-set! v 0 "apple") > v '#("apple" #f #f)
procedure
(dict-set dict key v) → (and/c dict? immutable?)
dict : (and/c dict? immutable?) key : any/c v : any/c
> (dict-set #hash() 'a "apple") '#hash((a . "apple"))
> (dict-set #hash((a . "apple") (b . "beer")) 'b "banana") '#hash((a . "apple") (b . "banana"))
> (dict-set '() 'a "apple") '((a . "apple"))
> (dict-set '((a . "apple") (b . "beer")) 'b "banana") '((a . "apple") (b . "banana"))
procedure
(dict-remove! dict key) → void?
dict : (and/c dict? (not/c immutable?)) key : any/c
> (define h (make-hash)) > (dict-set! h 'a "apple") > h '#hash((a . "apple"))
> (dict-remove! h 'a) > h '#hash()
procedure
(dict-remove dict key) → (and/c dict? immutable?)
dict : (and/c dict? immutable?) key : any/c
> (define h #hash()) > (define h (dict-set h 'a "apple")) > h '#hash((a . "apple"))
> (dict-remove h 'a) '#hash()
> h '#hash((a . "apple"))
> (dict-remove h 'z) '#hash((a . "apple"))
> (dict-remove '((a . "apple") (b . "banana")) 'a) '((b . "banana"))
procedure
(dict-iterate-first dict) → any/c
dict : dict?
> (dict-iterate-first #hash((a . "apple") (b . "banana"))) 0
> (dict-iterate-first #hash()) #f
> (dict-iterate-first #("apple" "banana")) 0
> (dict-iterate-first '((a . "apple") (b . "banana"))) #<assoc-iter>
procedure
(dict-iterate-next dict pos) → any/c
dict : dict? pos : any/c
> (define h #hash((a . "apple") (b . "banana"))) > (define i (dict-iterate-first h)) > i 0
> (dict-iterate-next h i) 1
> (dict-iterate-next h (dict-iterate-next h i)) #f
procedure
(dict-iterate-key dict pos) → any
dict : dict? pos : any/c
> (define h '((a . "apple") (b . "banana"))) > (define i (dict-iterate-first h)) > (dict-iterate-key h i) 'a
> (dict-iterate-key h (dict-iterate-next h i)) 'b
procedure
(dict-iterate-value dict pos) → any
dict : dict? pos : any/c
> (define h '((a . "apple") (b . "banana"))) > (define i (dict-iterate-first h)) > (dict-iterate-value h i) "apple"
> (dict-iterate-value h (dict-iterate-next h i)) "banana"
4.16.2.2 Derived Dictionary Methods
These methods of gen:dict have fallback implementations in terms of the other methods; they may be supported even by dictionary types that do not directly implement them.
procedure
(dict-has-key? dict key) → boolean?
dict : dict? key : any/c
Supported for any dict that implements dict-ref.
> (dict-has-key? #hash((a . "apple") (b . "beer")) 'a) #t
> (dict-has-key? #hash((a . "apple") (b . "beer")) 'c) #f
> (dict-has-key? '((a . "apple") (b . "banana")) 'b) #t
> (dict-has-key? #("apple" "banana") 1) #t
> (dict-has-key? #("apple" "banana") 3) #f
> (dict-has-key? #("apple" "banana") -3) #f
procedure
(dict-set*! dict key v ... ...) → void?
dict : (and/c dict? (not/c immutable?)) key : any/c v : any/c
Supported for any dict that implements dict-set!.
> (define h (make-hash)) > (dict-set*! h 'a "apple" 'b "banana") > h '#hash((a . "apple") (b . "banana"))
> (define v1 (vector #f #f #f)) > (dict-set*! v1 0 "apple" 1 "banana") > v1 '#("apple" "banana" #f)
> (define v2 (vector #f #f #f)) > (dict-set*! v2 0 "apple" 0 "banana") > v2 '#("banana" #f #f)
procedure
(dict-set* dict key v ... ...) → (and/c dict? immutable?)
dict : (and/c dict? immutable?) key : any/c v : any/c
Supported for any dict that implements dict-set.
> (dict-set* #hash() 'a "apple" 'b "beer") '#hash((a . "apple") (b . "beer"))
> (dict-set* #hash((a . "apple") (b . "beer")) 'b "banana" 'a "anchor") '#hash((a . "anchor") (b . "banana"))
> (dict-set* '() 'a "apple" 'b "beer") '((a . "apple") (b . "beer"))
> (dict-set* '((a . "apple") (b . "beer")) 'b "banana" 'a "anchor") '((a . "anchor") (b . "banana"))
> (dict-set* '((a . "apple") (b . "beer")) 'b "banana" 'b "ballistic") '((a . "apple") (b . "ballistic"))
Supported for any dict that implements dict-ref and dict-set!.
> (dict-ref! (make-hasheq '((a . "apple") (b . "beer"))) 'a #f) "apple"
> (dict-ref! (make-hasheq '((a . "apple") (b . "beer"))) 'c 'cabbage) 'cabbage
> (define h (make-hasheq '((a . "apple") (b . "beer")))) > (dict-ref h 'c) hash-ref: no value found for key
key: 'c
> (dict-ref! h 'c (λ () 'cabbage)) 'cabbage
> (dict-ref h 'c) 'cabbage
procedure
(dict-update! dict key updater [ failure-result]) → void? dict : (and/c dict? (not/c immutable?)) key : any/c updater : (any/c . -> . any/c)
failure-result : failure-result/c = (lambda () (raise (make-exn:fail ....)))
Supported for any dict that implements dict-ref and dict-set!.
> (define h (make-hash)) > (dict-update! h 'a add1) hash-update!: no value found for key: 'a
> (dict-update! h 'a add1 0) > h '#hash((a . 1))
> (define v (vector #f #f #f)) > (dict-update! v 0 not) > v '#(#t #f #f)
procedure
(dict-update dict key updater [failure-result])
→ (and/c dict? immutable?) dict : dict? key : any/c updater : (any/c . -> . any/c)
failure-result : failure-result/c = (lambda () (raise (make-exn:fail ....)))
Supported for any dict that implements dict-ref and dict-set.
> (dict-update #hash() 'a add1) hash-update: no value found for key: 'a
> (dict-update #hash() 'a add1 0) '#hash((a . 1))
> (dict-update #hash((a . "apple") (b . "beer")) 'b string-length) '#hash((a . "apple") (b . 4))
Supported for any dict that implements dict-iterate-first, dict-iterate-next, dict-iterate-key, and dict-iterate-value.
Supported for any dict that implements dict-iterate-first, dict-iterate-next, dict-iterate-key, and dict-iterate-value.
> (dict-for-each #hash((a . "apple") (b . "banana")) (lambda (k v) (printf "~a = ~s\n" k v)))
b = "banana"
a = "apple"
procedure
(dict-empty? dict) → boolean?
dict : dict?
Supported for any dict that implements dict-iterate-first.
> (dict-empty? #hash((a . "apple") (b . "banana"))) #f
> (dict-empty? (vector)) #t
procedure
(dict-count dict) → exact-nonnegative-integer?
dict : dict?
Supported for any dict that implements dict-iterate-first and dict-iterate-next.
> (dict-count #hash((a . "apple") (b . "banana"))) 2
> (dict-count #("apple" "banana")) 2
Supported for any dict that implements dict-clear, dict-set!, dict-iterate-first, dict-iterate-next, dict-iterate-key, and dict-iterate-value.
> (define original (vector "apple" "banana")) > (define copy (dict-copy original)) > original '#("apple" "banana")
> copy '#("apple" "banana")
> (dict-set! copy 1 "carrot") > original '#("apple" "banana")
> copy '#("apple" "carrot")
procedure
(dict-clear dict) → dict?
dict : dict?
Supported for any dict that supports dict-remove, dict-iterate-first, dict-iterate-next, and dict-iterate-key.
> (dict-clear #hash((a . "apple") ("banana" . b))) '#hash()
> (dict-clear '((1 . two) (three . "four"))) '()
procedure
(dict-clear! dict) → void?
dict : dict?
Supported for any dict that supports dict-remove!, dict-iterate-first, and dict-iterate-key.
> (define table (make-hash)) > (dict-set! table 'a "apple") > (dict-set! table "banana" 'b) > table '#hash((a . "apple") ("banana" . b))
> (dict-clear! table) > table '#hash()
Supported for any dict that implements dict-iterate-first, dict-iterate-next, and dict-iterate-key.
procedure
(dict-values dict) → list?
dict : dict?
Supported for any dict that implements dict-iterate-first, dict-iterate-next, and dict-iterate-value.
> (define h #hash((a . "apple") (b . "banana"))) > (dict-values h) '("banana" "apple")
procedure
(dict->list dict) → list?
dict : dict?
Supported for any dict that implements dict-iterate-first, dict-iterate-next, dict-iterate-key, and dict-iterate-value.
> (define h #hash((a . "apple") (b . "banana"))) > (dict->list h) '((b . "banana") (a . "apple"))
4.16.3 Dictionary Sequences
Supported for any dict that implements dict-iterate-first, dict-iterate-next, dict-iterate-key, and dict-iterate-value.
> (define h #hash((a . "apple") (b . "banana")))
> (for/list ([(k v) (in-dict h)]) (format "~a = ~s" k v)) '("b = \"banana\"" "a = \"apple\"")
procedure
(in-dict-keys dict) → sequence?
dict : dict?
Supported for any dict that implements dict-iterate-first, dict-iterate-next, and dict-iterate-key.
> (define h #hash((a . "apple") (b . "banana")))
> (for/list ([k (in-dict-keys h)]) k) '(b a)
procedure
(in-dict-values dict) → sequence?
dict : dict?
Supported for any dict that implements dict-iterate-first, dict-iterate-next, and dict-iterate-value.
> (define h #hash((a . "apple") (b . "banana")))
> (for/list ([v (in-dict-values h)]) v) '("banana" "apple")
procedure
(in-dict-pairs dict) → sequence?
dict : dict?
Supported for any dict that implements dict-iterate-first, dict-iterate-next, dict-iterate-key, and dict-iterate-value.
> (define h #hash((a . "apple") (b . "banana")))
> (for/list ([p (in-dict-pairs h)]) p) '((b . "banana") (a . "apple"))
4.16.4 Contracted Dictionaries
(list dict-vector (vector type-key-contract type-value-contract type-iter-contract instance-key-contract instance-value-contract instance-iter-contract))
The first vector must be a vector of 10 procedures which match the gen:dict generic interface (in addition, it must be an immutable vector). The second vector must contain six elements; each of the first three is a contract for the dictionary type’s keys, values, and positions, respectively. Each of the second three is either #f or a procedure used to extract the contract from a dictionary instance.
procedure
(dict-key-contract d) → contract?
d : dict?
procedure
(dict-value-contract d) → contract?
d : dict?
procedure
(dict-iter-contract d) → contract?
d : dict?
4.16.5 Custom Hash Tables
syntax
(define-custom-hash-types name optional-predicate comparison-expr optional-hash-functions)
optional-predicate =
| #:key? predicate-expr optional-hash-functions =
| hash1-expr | hash1-expr hash2-expr
Defines seven names:
name? recognizes instances of the new type,
immutable-name? recognizes immutable instances of the new type,
mutable-name? recognizes mutable instances of the new type with strongly-held keys,
weak-name? recognizes mutable instances of the new type with weakly-held keys,
make-immutable-name constructs immutable instances of the new type,
make-mutable-name constructs mutable instances of the new type with strongly-held keys, and
make-weak-name constructs mutable instances of the new type with weakly-held keys.
The constructors all accept a dictionary as an optional argument, providing initial key/value pairs.
> (define-custom-hash-types string-hash #:key? string? string=? string-length)
> (define imm (make-immutable-string-hash '(("apple" . a) ("banana" . b))))
> (define mut (make-mutable-string-hash '(("apple" . a) ("banana" . b)))) > (dict? imm) #t
> (dict? mut) #t
> (string-hash? imm) #t
> (string-hash? mut) #t
> (immutable-string-hash? imm) #t
> (immutable-string-hash? mut) #f
> (dict-ref imm "apple") 'a
> (dict-ref mut "banana") 'b
> (dict-set! mut "banana" 'berry) > (dict-ref mut "banana") 'berry
> (equal? imm mut) #f
> (equal? (dict-remove (dict-remove imm "apple") "banana") (make-immutable-string-hash)) #t
procedure
(make-custom-hash-types eql? [ hash1 hash2 #:key? key? #:name name #:for who]) →
(any/c . -> . boolean?) (any/c . -> . boolean?) (any/c . -> . boolean?) (any/c . -> . boolean?) (->* [] [dict?] dict?) (->* [] [dict?] dict?) (->* [] [dict?] dict?)
eql? :
(or/c (any/c any/c . -> . any/c) (any/c any/c (any/c any/c . -> . any/c) . -> . any/c))
hash1 :
(or/c (any/c . -> . exact-integer?) (any/c (any/c . -> . exact-integer?) . -> . exact-integer?)) = (const 1)
hash2 :
(or/c (any/c . -> . exact-integer?) (any/c (any/c . -> . exact-integer?) . -> . exact-integer?)) = (const 1) key? : (any/c . -> . boolean?) = (const #true) name : symbol? = 'custom-hash who : symbol? = 'make-custom-hash-types
The comparison function eql? may accept 2 or 3 arguments. If it accepts 2 arguments, it given two keys to compare them. If it accepts 3 arguments and does not accept 2 arguments, it is also given a recursive comparison function that handles data cycles when comparing sub-parts of the keys.
The hash functions hash1 and hash2 may accept 1 or 2 arguments. If either hash function accepts 1 argument, it is applied to a key to compute the corresponding hash value. If either hash function accepts 2 arguments and does not accept 1 argument, it is also given a recursive hash function that handles data cycles when computing hash values of sub-parts of the keys.
The predicate key? must accept 1 argument and is used to recognize valid keys for the new dictionary type.
Produces seven values:
a predicate recognizing all instances of the new dictionary type,
a predicate recognizing immutable instances,
a predicate recognizing mutable instances,
a predicate recognizing weak instances,
a constructor for immutable instances,
a constructor for mutable instances, and
a constructor for weak instances.
See define-custom-hash-types for an example.
The make-custom-hash and make-weak-custom-hash functions create a mutable dictionary that does not support functional update, while make-immutable-custom-hash creates an immutable dictionary that supports functional update. The dictionary created by make-weak-custom-hash retains its keys weakly, like the result of make-weak-hash.
Dictionaries created by make-custom-hash and company are equal? when they have the same mutability and key strength, the associated procedures are equal?, and the key–value mappings are the same when keys and values are compared with equal?.
See also define-custom-hash-types.
> (define h (make-custom-hash (lambda (a b) (string=? (format "~a" a) (format "~a" b))) (lambda (a) (equal-hash-code (format "~a" a))))) > (dict-set! h 1 'one) > (dict-ref h "1") 'one
4.16.6 Passing keyword arguments in dictionaries
procedure
(keyword-apply/dict proc kw-dict pos-arg ... pos-args #:<kw> kw-arg ...) → any proc : procedure? kw-dict : dict? pos-arg : any/c pos-args : (listof any/c) kw-arg : any/c
All the keys in kw-dict must be keywords. The keywords in the kw-dict do not have to be sorted. However, the keywords in kw-dict and the directly supplied #:<kw> keywords must not overlap. The given proc must accept all of the keywords in kw-dict plus the #:<kw>s.
> (define (sundae #:ice-cream [ice-cream '("vanilla")] #:toppings [toppings '("brownie-bits")] #:sprinkles [sprinkles "chocolate"] #:syrup [syrup "caramel"]) (format "A sundae with ~a ice cream, ~a, ~a sprinkles, and ~a syrup." (string-join ice-cream #:before-last " and ") (string-join toppings #:before-last " and ") sprinkles syrup)) > (keyword-apply/dict sundae '((#:ice-cream "chocolate")) '()) "A sundae with chocolate ice cream, brownie-bits, chocolate sprinkles, and caramel syrup."
> (keyword-apply/dict sundae (hash '#:toppings '("cookie-dough") '#:sprinkles "rainbow" '#:syrup "chocolate") '()) "A sundae with vanilla ice cream, cookie-dough, rainbow sprinkles, and chocolate syrup."
> (keyword-apply/dict sundae #:sprinkles "rainbow" (hash '#:toppings '("cookie-dough") '#:syrup "chocolate") '()) "A sundae with vanilla ice cream, cookie-dough, rainbow sprinkles, and chocolate syrup."
Added in version 7.9 of package base.