사전
사전 (Dictionaries)
키를 값에 매핑하는 데이터 타입인 사전(dictionary)을 알아봐요. 해시 테이블·벡터·연관 리스트 등 다양한 사전을 공통 인터페이스로 다루는 racket/dict 라이브러리를 살펴봅니다.
출처: Racket Reference
본문
4.18 사전 (Dictionaries)
사전은 키를 값에 매핑하는 데이터 타입의 한 인스턴스예요. 다음 데이터 타입들은 모두 사전입니다:
-
vector (키로 정확한 정수만 사용);
-
pair들의 list를, 키를 equal?로 비교하며 키가 서로 구별되어야 하는 연관 리스트(association list)로 사용; 그리고
pair들의 목록을 연관 리스트로 쓰되 키가 서로 구별되지 않는 경우(즉 연관 리스트가 아닌 경우), dict-ref와 dict-remove 같은 연산은 키의 첫 번째 인스턴스에 동작하고, dict-map과 dict-keys 같은 연산은 키의 모든 인스턴스에 대해 요소를 만듭니다.
(require racket/dict)
이 절에 문서화된 바인딩은 racket/dict와 racket 라이브러리가 제공하지만 racket/base는 제공하지 않아요.
4.18.1 사전 술어와 컨트랙트
(dict? v) → boolean?
v : any/c
v가 사전이면 #t, 아니면 #f를 반환합니다.
dict?는 pair에 대해 상수 시간 검사가 아니라는 점을 주의하세요. v가 연관 리스트인지 확인하려면 목록을 순회해야 할 수 있기 때문이에요.
(dict? #hash((a . "apple")))
; #t
(dict? '#("apple" "banana"))
; #t
(dict? '("apple" "banana"))
; #f
(dict? '((a . "apple") (b . "banana")))
; #t
(dict-implements? d sym ...) → boolean?
d : dict?
sym : symbol?
d가 sym들이 이름을 붙인 gen:dict의 모든 메서드를 구현하면 #t, 아니면 #f를 반환합니다. 폴백(fallback) 구현은 결과에 영향을 주지 않아요. d가 폴백 구현으로 주어진 메서드들을 지원할 수 있음에도 #f를 만들 수 있습니다.
(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
(dict-implements/c sym ...) → flat-contract?
sym : symbol?
sym들이 이름을 붙인 gen:dict의 모든 메서드를 지원하는 사전을 인식합니다. 생성된 컨트랙트는 hash/c와 비슷하지 않고, dict-implements?에 더 가깝다는 점을 참고하세요.
(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
(dict-mutable? d) → boolean?
d : dict?
d가 dict-set!로 변형 가능하면 #t, 아니면 #f를 반환합니다.
(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
(dict-can-remove-keys? d) → boolean?
d : dict?
d가 dict-remove! 및/또는 dict-remove를 통해 매핑 제거를 지원하면 #t, 아니면 #f를 반환합니다.
(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
(dict-can-functional-set? d) → boolean?
d : dict?
d가 dict-set를 통한 함수적 갱신을 지원하면 #t, 아니면 #f를 반환합니다.
(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.18.2 제네릭 사전 인터페이스
gen:dict
struct 정의의 #:methods 옵션을 통해 구조체 타입에 사전 메서드 구현을 공급하는 제네릭 인터페이스입니다. 이 인터페이스는 원시 사전 메서드(4.18.2.1)와 파생 사전 메서드(4.18.2.2)로 문서화된 메서드 중 어떤 것이든 구현하는 데 사용할 수 있어요.
(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)))
; 등 기타 다른 메서드
])
(define d1 (alist '((1 . a) (2 . b))))
(dict? d1)
; #t
(dict-ref d1 1)
; 'a
(dict-remove d1 1)
; #<alist>
prop:dict : struct-type-property?
사전 API에 사용자 정의 확장을 추가하는 데 쓰는 구조체 타입 프로퍼티입니다. prop:dict 프로퍼티 대신 gen:dict 제네릭 인터페이스를 사용하는 것이 권장됩니다. 10개의 메서드 구현이 담긴 벡터를 받아요:
-
dict-ref
-
dict-set!, 또는 지원하지 않으면 #f
-
dict-set, 또는 지원하지 않으면 #f
-
dict-remove!, 또는 지원하지 않으면 #f
-
dict-remove, 또는 지원하지 않으면 #f
-
dict-count
-
dict-iterate-first
-
dict-iterate-next
-
dict-iterate-key
-
dict-iterate-value
4.18.2.1 원시 사전 메서드
이 gen:dict 메서드들은 폴백 구현이 없어요. 직접 구현하는 사전 타입에서만 지원됩니다.
(dict-ref dict key [failure-result]) → any
dict : dict?
key : any/c
failure-result : failure-result/c = (lambda () (raise (make-exn:fail ....)))
dict에서 key의 값을 반환합니다. key에 대한 값이 없으면 failure-result가 결과를 결정합니다:
-
failure-result가 프로시저이면 (꼬리 호출로) 인자 없이 호출되어 결과를 만듭니다. -
그렇지 않으면
failure-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
(dict-set! dict key v) → void?
dict : (and/c dict? (not/c immutable?))
key : any/c
v : any/c
dict에서 key를 v에 매핑하며, 기존의 key 매핑은 덮어씁니다. dict가 변형 가능하지 않거나 key가 사전에 허용된 키가 아니면(예: dict가 vector일 때 적절한 범위의 정확한 정수가 아닌 경우) 갱신은 exn:fail:contract 예외로 실패할 수 있어요.
(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)
(dict-set dict key v) → (and/c dict? immutable?)
dict : (and/c dict? immutable?)
key : any/c
v : any/c
dict를 함수적으로 확장해 key를 v에 매핑하고, 기존의 key 매핑은 덮어쓰며, 확장된 사전을 반환합니다. dict가 함수적 확장을 지원하지 않거나 key가 사전에 허용된 키가 아니면 갱신은 exn:fail:contract 예외로 실패할 수 있어요.
(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"))
(dict-remove! dict key) → void?
dict : (and/c dict? (not/c immutable?))
key : any/c
dict에서 key의 기존 매핑을 제거합니다. dict가 변형 가능하지 않거나 키 제거를 지원하지 않으면(예: vector의 경우) 갱신은 실패할 수 있어요.
(define h (make-hash))
(dict-set! h 'a "apple")
h
; '#hash((a . "apple"))
(dict-remove! h 'a)
h
; '#hash()
(dict-remove dict key) → (and/c dict? immutable?)
dict : (and/c dict? immutable?)
key : any/c
dict에서 key의 기존 매핑을 함수적으로 제거하고, 새 사전을 반환합니다. dict가 함수적 갱신을 지원하지 않거나 키 제거를 지원하지 않으면 갱신은 실패할 수 있어요.
(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"))
(dict-iterate-first dict) → any/c
dict : dict?
dict에 요소가 없으면 #f, 그렇지 않으면 dict 테이블에서 첫 번째 요소의 인덱스인 #f가 아닌 값을 반환합니다. 여기서 "첫 번째"란 사전 요소의 불특정 순서를 가리켜요. 변형 가능한 사전의 경우, 이 인덱스는 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>
(dict-iterate-next dict pos) → any/c
dict : dict?
pos : any/c
pos가 가리키는 요소 다음의 dict 요소 인덱스인 #f가 아닌 값을 반환하거나, pos가 dict의 마지막 요소를 가리키면 #f를 반환합니다. pos가 유효한 인덱스가 아니면 exn:fail:contract 예외가 발생합니다. 변형 가능한 사전의 경우 결과 인덱스는 dict에 항목이 추가되거나 제거되지 않는 동안에만 그 항목을 가리키는 것이 보장됩니다. dict-iterate-next 연산은 상수 시간이 걸려야 해요.
(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
(dict-iterate-key dict pos) → any
dict : dict?
pos : any/c
dict에서 인덱스 pos에 있는 요소의 키를 반환합니다. pos가 dict에 유효한 인덱스가 아니면 exn:fail:contract 예외가 발생합니다. dict-iterate-key 연산은 상수 시간이 걸려야 해요.
(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
(dict-iterate-value dict pos) → any
dict : dict?
pos : any/c
dict에서 인덱스 pos에 있는 요소의 값을 반환합니다. pos가 dict에 유효한 인덱스가 아니면 exn:fail:contract 예외가 발생합니다. dict-iterate-key 연산은 상수 시간이 걸려야 해요.
(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.18.2.2 파생 사전 메서드
이 gen:dict 메서드들은 다른 메서드 관점에서 폴백 구현을 가져요. 직접 구현하지 않는 사전 타입에서도 지원될 수 있습니다.
(dict-has-key? dict key) → boolean?
dict : dict?
key : any/c
dict가 주어진 키에 대한 값을 담고 있으면 #t, 아니면 #f를 반환합니다.
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
(dict-set*! dict key v ...) → void?
dict : (and/c dict? (not/c immutable?))
key : any/c
v : any/c
dict에서 각 key를 각 v에 매핑하며, 각 키의 기존 매핑은 덮어씁니다. dict가 변형 가능하지 않거나 어떤 키가 사전에 허용된 키가 아니면(예: vector일 때 적절한 범위의 정확한 정수가 아닌 경우) 갱신은 exn:fail:contract 예외로 실패할 수 있어요. 갱신은 왼쪽에서 진행되므로 나중 매핑이 앞선 매핑을 덮어씁니다.
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)
(dict-set* dict key v ...) → (and/c dict? immutable?)
dict : (and/c dict? immutable?)
key : any/c
v : any/c
dict를 함수적으로 확장해 각 key를 각 v에 매핑하고, 각 키의 기존 매핑은 덮어쓰며, 확장된 사전을 반환합니다. dict가 함수적 확장을 지원하지 않거나 어떤 키가 사전에 허용된 키가 아니면 갱신은 exn:fail:contract 예외로 실패할 수 있어요. 갱신은 왼쪽에서 진행되므로 나중 매핑이 앞선 매핑을 덮어씁니다.
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"))
(dict-ref! dict key to-set) → any
dict : dict?
key : any/c
to-set : any/c
dict에서 key의 값을 반환합니다. key에 대한 값이 없으면 to-set이 dict-ref에서처럼 결과를 결정하고(즉 값을 계산하는 thunk이거나 평범한 값), 이 결과가 그 키에 대해 dict에 저장됩니다. (to-set이 thunk라면 꼬리 위치에서 호출되지 않는다는 점에 주의하세요.)
dict-ref와 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
(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 ....)))
dict-ref와 dict-set!를 조합해 dict의 기존 매핑을 갱신합니다. 선택적 failure-result 인자는 key에 대한 매핑이 이미 없을 때 dict-ref에서처럼 사용됩니다.
dict-ref와 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)
(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 ....)))
dict-ref와 dict-set을 조합해 dict의 기존 매핑을 함수적으로 갱신합니다. 선택적 failure-result 인자는 key에 대한 매핑이 이미 없을 때 dict-ref에서처럼 사용됩니다.
dict-ref와 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))
(dict-map dict proc) → (listof any/c)
dict : dict?
proc : (any/c any/c . -> . any/c)
proc 프로시저를 dict의 각 요소에 불특정 순서로 적용하고, 결과를 목록에 누적합니다. proc 프로시저는 키와 그 값으로 매번 호출됩니다.
dict-iterate-first, dict-iterate-next, dict-iterate-key, dict-iterate-value를 구현한 어떤 사전에서든 지원됩니다.
(dict-map #hash((a . "apple") (b . "banana")) vector)
; '(#(b "banana") #(a "apple"))
(dict-map/copy dict proc) → dict?
dict : dict?
proc : (any/c any/c . -> . (values any/c any/c))
proc 프로시저를 dict의 각 요소에 불특정 순서로 적용하고, 결과를 같은 종류의 사전에 누적합니다. proc 프로시저는 키와 그 값으로 매번 호출되고, 대응하는 키와 값을 반환해야 합니다.
dict-iterate-first, dict-iterate-next, dict-iterate-key, dict-iterate-value와, dict-set·dict-clear 또는 dict-set!·dict-copy·dict-clear!를 구현한 어떤 사전에서든 지원됩니다.
(dict-map/copy #hash((a . "apple") (b . "banana")) (lambda (k v) (values k (string-upcase v))))
; '#hash((a . "APPLE") (b . "BANANA"))
Added in version 8.5.0.2 of package base.
(dict-for-each dict proc) → void?
dict : dict?
proc : (any/c any/c . -> . any)
proc를 dict의 각 요소에 (proc의 부수 효과를 위해) 불특정 순서로 적용합니다. proc 프로시저는 키와 그 값으로 매번 호출됩니다.
dict-iterate-first, dict-iterate-next, dict-iterate-key, dict-iterate-value를 구현한 어떤 사전에서든 지원됩니다.
(dict-for-each #hash((a . "apple") (b . "banana"))
(lambda (k v)
(printf "~a = ~s\n" k v)))
; b = "banana"
; a = "apple"
(dict-empty? dict) → boolean?
dict : dict?
dict가 비어 있는지 보고합니다.
dict-iterate-first를 구현한 어떤 사전에서든 지원됩니다.
(dict-empty? #hash((a . "apple") (b . "banana")))
; #f
(dict-empty? (vector))
; #t
(dict-count dict) → exact-nonnegative-integer?
dict : dict?
dict가 매핑한 키의 개수를, 보통 상수 시간에 반환합니다.
dict-iterate-first와 dict-iterate-next를 구현한 어떤 사전에서든 지원됩니다.
(dict-count #hash((a . "apple") (b . "banana")))
; 2
(dict-count #("apple" "banana"))
; 2
(dict-copy dict) → dict?
dict : dict?
dict와 같은 타입이고 같은 키/값 연관을 가진 새 변형 가능한 사전을 만듭니다.
dict-clear, dict-set!, dict-iterate-first, dict-iterate-next, dict-iterate-key, 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")
(dict-clear dict) → dict?
dict : dict?
dict와 같은 타입의 빈 사전을 만듭니다. dict가 변형 가능하면 결과는 새 사전이어야 해요.
dict-remove, dict-iterate-first, dict-iterate-next, dict-iterate-key를 지원하는 어떤 사전에서든 지원됩니다.
(dict-clear #hash((a . "apple") ("banana" . b)))
; '#hash()
(dict-clear '((1 . two) (three . "four")))
; '()
(dict-clear! dict) → void?
dict : dict?
dict의 모든 키/값 연관을 제거합니다.
dict-remove!, dict-iterate-first, 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()
(dict-keys dict) → list?
dict : dict?
dict의 키 목록을 불특정 순서로 반환합니다.
dict-iterate-first, dict-iterate-next, dict-iterate-key를 구현한 어떤 사전에서든 지원됩니다.
(define h #hash((a . "apple") (b . "banana")))
(dict-keys h)
; '(b a)
(dict-values dict) → list?
dict : dict?
dict의 값 목록을 불특정 순서로 반환합니다.
dict-iterate-first, dict-iterate-next, dict-iterate-value를 구현한 어떤 사전에서든 지원됩니다.
(define h #hash((a . "apple") (b . "banana")))
(dict-values h)
; '("banana" "apple")
(dict->list dict) → list?
dict : dict?
dict의 연관 목록을 불특정 순서로 반환합니다.
dict-iterate-first, dict-iterate-next, dict-iterate-key, dict-iterate-value를 구현한 어떤 사전에서든 지원됩니다.
(define h #hash((a . "apple") (b . "banana")))
(dict->list h)
; '((b . "banana") (a . "apple"))
4.18.3 사전 시퀀스
(in-dict dict) → sequence?
dict : dict?
각 요소가 dict의 키와 대응하는 값이라는 두 값을 가진 sequence를 반환합니다.
dict-iterate-first, dict-iterate-next, dict-iterate-key, 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\"")
(in-dict-keys dict) → sequence?
dict : dict?
요소가 dict의 키들인 시퀀스를 반환합니다.
dict-iterate-first, dict-iterate-next, dict-iterate-key를 구현한 어떤 사전에서든 지원됩니다.
(define h #hash((a . "apple") (b . "banana")))
(for/list ([k (in-dict-keys h)])
k)
; '(b a)
(in-dict-values dict) → sequence?
dict : dict?
요소가 dict의 값들인 시퀀스를 반환합니다.
dict-iterate-first, dict-iterate-next, dict-iterate-value를 구현한 어떤 사전에서든 지원됩니다.
(define h #hash((a . "apple") (b . "banana")))
(for/list ([v (in-dict-values h)])
v)
; '("banana" "apple")
(in-dict-pairs dict) → sequence?
dict : dict?
요소가 pair들이고, 각 pair가 dict의 키와 그 값을 담고 있는 시퀀스를 반환합니다. (이와 달리 in-dict는 각 요소에 대해 키와 값을 별개의 값으로 가져와요.)
dict-iterate-first, dict-iterate-next, dict-iterate-key, dict-iterate-value를 구현한 어떤 사전에서든 지원됩니다.
(define h #hash((a . "apple") (b . "banana")))
(for/list ([p (in-dict-pairs h)])
p)
; '((b . "banana") (a . "apple"))
4.18.4 컨트랙트된 사전
prop:dict/contract : struct-type-property?
컨트랙트를 가진 사전을 정의하기 위한 구조체 타입 프로퍼티입니다. prop:dict/contract와 연관된 값은 두 개의 불변 벡터 목록이어야 해요:
(list dict-vector
(vector type-key-contract
type-value-contract
type-iter-contract
instance-key-contract
instance-value-contract
instance-iter-contract))
첫 번째 벡터는 gen:dict 제네릭 인터페이스와 일치하는 10개 프로시저의 벡터여야 합니다(또한 불변 벡터여야 해요). 두 번째 벡터는 여섯 요소를 담아야 하며, 앞의 세 개는 각각 사전 타입의 키·값·위치에 대한 컨트랙트입니다. 뒤의 세 개는 각각 #f이거나 사전 인스턴스에서 컨트랙트를 추출하는 데 쓰는 프로시저입니다.
(dict-key-contract d) → contract?
d : dict?
(dict-value-contract d) → contract?
d : dict?
(dict-iter-contract d) → contract?
d : dict?
각각, d가 prop:dict/contract 인터페이스를 구현하면 d가 자신의 키·값·이터레이터에 부과하는 컨트랙트를 반환합니다.
4.18.5 사용자 정의 해시 테이블
(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
주어진 비교 함수 comparison-expr, 해시 함수 hash1-expr와 hash2-expr, 키 술어 predicate-expr에 기반한 새 사전 타입을 만듭니다. 이 함수들의 인터페이스는 make-custom-hash-types에서와 같아요. 새 사전 타입은 세 가지 변형이 있어요: 불변, 키를 강하게 쥐는 변형 가능, 키를 약하게 쥐는 변형 가능.
일곱 개의 이름을 정의합니다:
-
name?는 새 타입의 인스턴스를 인식하고, -
immutable-name?는 새 타입의 불변 인스턴스를 인식하고, -
mutable-name?는 키를 강하게 쥔 새 타입의 변형 가능 인스턴스를 인식하고, -
weak-name?는 키를 약하게 쥔 새 타입의 변형 가능 인스턴스를 인식하고, -
make-immutable-name은 새 타입의 불변 인스턴스를 만들고, -
make-mutable-name은 키를 강하게 쥔 새 타입의 변형 가능 인스턴스를 만들고, -
make-weak-name은 키를 약하게 쥔 새 타입의 변형 가능 인스턴스를 만듭니다.
생성자들은 모두 선택적 인자로 사전을 받아 초기 키/값 쌍을 제공합니다.
(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
(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
주어진 비교 함수 eql?, 해시 함수 hash1과 hash2, 술어 key?에 기반한 새 사전 타입을 만듭니다. 새 사전 타입은 불변, 키를 강하게 쥔 변형 가능, 키를 약하게 쥔 변형 가능인 변형들이 있어요. 주어진 name은 새 사전 타입의 인스턴스를 출력할 때 사용되고, 심볼 who는 오류 메시지를 낼 때 사용됩니다.
비교 함수 eql?는 2개 또는 3개의 인자를 받을 수 있어요. 2개를 받으면 두 키가 주어져 비교합니다. 3개를 받고 2개를 받지 않는다면, 키의 하위 부분을 비교할 때 데이터 순환을 다루는 재귀 비교 함수도 함께 주어집니다.
해시 함수 hash1과 hash2는 1개 또는 2개의 인자를 받을 수 있어요. 어느 해시 함수든 1개를 받으면 키에 적용되어 대응하는 해시 값을 계산합니다. 어느 해시 함수든 2개를 받고 1개를 받지 않는다면, 키의 하위 부분의 해시 값을 계산할 때 데이터 순환을 다루는 재귀 해시 함수도 함께 주어집니다.
술어 key?는 1개의 인자를 받아야 하고, 새 사전 타입의 유효한 키를 인식하는 데 사용됩니다.
일곱 개의 값을 만듭니다:
-
새 사전 타입의 모든 인스턴스를 인식하는 술어,
-
불변 인스턴스를 인식하는 술어,
-
변형 가능 인스턴스를 인식하는 술어,
-
약한 인스턴스를 인식하는 술어,
-
불변 인스턴스용 생성자,
-
변형 가능 인스턴스용 생성자, 그리고
-
약한 인스턴스용 생성자.
define-custom-hash-types에서 예시를 보세요.
(make-custom-hash eql?
[hash1 hash2
#:key? key?])
→ 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?) = (λ (x) #true)
(make-weak-custom-hash eql?
[hash1 hash2
#:key? key?])
→ dict?
(make-immutable-custom-hash eql?
[hash1 hash2
#:key? key?])
→ dict?
키를 eql?로 비교하고 hash1과 hash2로 해시하며 키 술어가 key?인 해시 테이블로 구현된 새 사전 타입의 인스턴스를 만듭니다. 적절한 평등 및 해시 함수에 대한 정보는 gen:equal-mode+hash와 gen:equal+hash를 참고하세요.
make-custom-hash와 make-weak-custom-hash 함수는 함수적 갱신을 지원하지 않는 변형 가능한 사전을 만드는 반면, make-immutable-custom-hash는 함수적 갱신을 지원하는 불변 사전을 만듭니다. make-weak-custom-hash가 만든 사전은 make-weak-hash의 결과처럼 키를 약하게 유지해요.
make-custom-hash와 그 친구들이 만든 사전들은, 변형 가능성과 키 강도가 같고 연관된 프로시저가 equal?이고 키-값 매핑이 equal?로 키와 값을 비교했을 때 같으면 equal?입니다.
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.18.6 사전에서 키워드 인자 전달하기
(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
(list* pos-arg ... pos-args)의 위치 인자와, 직접 공급된 #:<kw> kw-arg 시퀀스의 키워드 인자에 더해 kw-dict의 키워드 인자를 사용해 proc을 적용합니다.
kw-dict의 모든 키는 키워드여야 해요. kw-dict의 키워드는 정렬될 필요가 없습니다. 그러나 kw-dict의 키워드와 직접 공급된 #:<kw> 키워드는 겹치면 안 됩니다. 주어진 proc은 kw-dict의 모든 키워드에 더해 #:<kw>들을 모두 받아들여야 해요.
(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.