사전

사전 (Dictionaries)

키를 값에 매핑하는 데이터 타입인 사전(dictionary)을 알아봐요. 해시 테이블·벡터·연관 리스트 등 다양한 사전을 공통 인터페이스로 다루는 racket/dict 라이브러리를 살펴봅니다.

출처: Racket Reference

본문

4.18 사전 (Dictionaries)

사전은 키를 값에 매핑하는 데이터 타입의 한 인스턴스예요. 다음 데이터 타입들은 모두 사전입니다:

pair들의 목록을 연관 리스트로 쓰되 키가 서로 구별되지 않는 경우(즉 연관 리스트가 아닌 경우), dict-refdict-remove 같은 연산은 키의 첫 번째 인스턴스에 동작하고, dict-mapdict-keys 같은 연산은 키의 모든 인스턴스에 대해 요소를 만듭니다.

(require racket/dict)

이 절에 문서화된 바인딩은 racket/dictracket 라이브러리가 제공하지만 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?

dsym들이 이름을 붙인 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?

ddict-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?

ddict-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?

ddict-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에서 keyv에 매핑하며, 기존의 key 매핑은 덮어씁니다. dict가 변형 가능하지 않거나 key가 사전에 허용된 키가 아니면(예: dictvector일 때 적절한 범위의 정확한 정수가 아닌 경우) 갱신은 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를 함수적으로 확장해 keyv에 매핑하고, 기존의 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가 아닌 값을 반환하거나, posdict의 마지막 요소를 가리키면 #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에 있는 요소의 키를 반환합니다. posdict에 유효한 인덱스가 아니면 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에 있는 요소의 값을 반환합니다. posdict에 유효한 인덱스가 아니면 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-setdict-ref에서처럼 결과를 결정하고(즉 값을 계산하는 thunk이거나 평범한 값), 이 결과가 그 키에 대해 dict에 저장됩니다. (to-set이 thunk라면 꼬리 위치에서 호출되지 않는다는 점에 주의하세요.)

dict-refdict-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-refdict-set!를 조합해 dict의 기존 매핑을 갱신합니다. 선택적 failure-result 인자는 key에 대한 매핑이 이미 없을 때 dict-ref에서처럼 사용됩니다.

dict-refdict-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-refdict-set을 조합해 dict의 기존 매핑을 함수적으로 갱신합니다. 선택적 failure-result 인자는 key에 대한 매핑이 이미 없을 때 dict-ref에서처럼 사용됩니다.

dict-refdict-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)

procdict의 각 요소에 (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-firstdict-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?

각각, dprop: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-exprhash2-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?, 해시 함수 hash1hash2, 술어 key?에 기반한 새 사전 타입을 만듭니다. 새 사전 타입은 불변, 키를 강하게 쥔 변형 가능, 키를 약하게 쥔 변형 가능인 변형들이 있어요. 주어진 name은 새 사전 타입의 인스턴스를 출력할 때 사용되고, 심볼 who는 오류 메시지를 낼 때 사용됩니다.

비교 함수 eql?는 2개 또는 3개의 인자를 받을 수 있어요. 2개를 받으면 두 키가 주어져 비교합니다. 3개를 받고 2개를 받지 않는다면, 키의 하위 부분을 비교할 때 데이터 순환을 다루는 재귀 비교 함수도 함께 주어집니다.

해시 함수 hash1hash2는 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?로 비교하고 hash1hash2로 해시하며 키 술어가 key?인 해시 테이블로 구현된 새 사전 타입의 인스턴스를 만듭니다. 적절한 평등 및 해시 함수에 대한 정보는 gen:equal-mode+hashgen:equal+hash를 참고하세요.

make-custom-hashmake-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> 키워드는 겹치면 안 됩니다. 주어진 prockw-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.

더 알아보기