dict — 사전 다루기
dict — 사전 다루기
키와 값을 짝지어 데이터를 저장해야 할 때가 많아요. 파일 확장자별 설명을 매핑하거나, 직원 정보처럼 여러 필드를 한 묶음으로 관리하고 싶을 때요. Tcl의 dict는 이런 키-값 매핑을 효율적이고 순서 보존 방식으로 다루는 명령이에요.
출처: Tcl 공식 문서 — dict
본문
시그니처
dict option arg ?arg ...?
설명
option에 따라 사전 값 또는 사전 값을 담은 변수에 대해 여러 연산 중 하나를 수행해요(아래 DICTIONARY VALUES 절 참고). 합법적인 옵션(축약 가능)은 다음과 같아요.
주요 옵션
dict append dictionaryVariable key ?string ...?
주어진 변수에 담긴 사전 값에서 key가 매핑하는 값에 주어진 문자열을 덧붙여, 결과 사전 값을 그 변수에 다시 써요. 존재하지 않는 키는 빈 문자열에 매핑된 것으로 취급돼요. 갱신된 사전 값을 반환해요.
dict create ?key value ...?
인자로 나열된 각 key/value 매핑(키와 값이 번갈아 나오고, 각 키 뒤에 그 값이 따라옴)을 담은 새 사전을 반환해요.
dict exists dictionaryValue key ?key ...?
주어진 key(또는 중첩 사전을 통한 키 경로)가 주어진 사전 값에 존재하는지 나타내는 불리언을 반환해요. dict get이 그 경로에서 성공할 때 정확히 참을 반환해요.
dict filter dictionaryValue filterType arg ?arg ...?
사전 값을 받아 지정한 필터 타입과 일치하는 key/value 쌍만 담긴 새 사전을 반환해요(필터 타입은 축약 가능). 지원하는 필터 타입은 다음과 같아요.
dict filter dictionaryValue key ?globPattern ...?
key 규칙은 키가 주어진 패턴 중 하나와 일치하는(string match 스타일) key/value 쌍만 매칭해요.
dict filter dictionaryValue script {keyVariable valueVariable} script
script 규칙은 키를 keyVariable에, 값을 valueVariable에 할당한 뒤, 불리언 결과를 내야 하는 스크립트를 평가해 매칭을 테스트해요(참이 반환될 때만 그 key/value 쌍이 dict filter 결과에 포함돼요). 규칙 선택 단어 다음의 첫 인자는 두 요소짜리 목록이에요. 스크립트가 TCL_BREAK 조건으로 반환하면 더는 key/value 쌍을 결과 사전에 넣지 않고, TCL_CONTINUE 조건은 거짓 결과와 동등해요. key/value 쌍은 키가 사전에 삽입된 순서대로 테스트돼요.
dict filter dictionaryValue value ?globPattern ...?
value 규칙은 값이 주어진 패턴 중 하나와 일치하는 key/value 쌍만 매칭해요.
dict for {keyVariable valueVariable} dictionaryValue body
이 명령은 인자 세 개를 받아요. 첫째는 사전의 각 매핑에 대한 키·값 변수 이름의 두 요소 목록, 둘째는 순회할 사전 값, 셋째는 foreach처럼 키·값 변수를 적절히 설정해 각 매핑마다 평가할 스크립트예요. 명령 결과는 빈 문자열이에요. 본문 평가가 TCL_BREAK 결과를 내면 더는 사전의 쌍을 순회하지 않고 dict for 명령은 즉시 성공적으로 끝나요. 본문 평가가 TCL_CONTINUE를 내면 정상 TCL_OK 결과처럼 취급돼요. 순회 순서는 키가 사전에 삽입된 순서예요.
dict get dictionaryValue ?key ...?
사전 값(첫 인자)과 키(둘째 인자)가 주어지면 그 키의 값을 얻어요. 키가 여럿이면 dict get $dictVal $key의 결과를 첫 인자로, 나머지 인자를 둘째(이후) 인자로 넘겨 dict get을 호출한 것처럼 동작해요. 이는 중첩 사전 조회를 쉽게 해 줘요. 예를 들어 다음 두 명령은 동등해요:
dict get $dict foo bar spong
dict get [dict get [dict get $dict foo] bar] spong
키를 주지 않으면 dict get은 array get과 비슷하게 요소 쌍으로 된 목록을 반환해요. 즉 각 쌍의 첫 요소가 키, 둘째 요소가 그 키의 값이에요.
사전에 없는 키의 값을 얻으려 하면 에러예요.
dict incr dictionaryVariable key ?increment?
주어진 변수의 사전 값에서 key가 매핑하는 값에 주어진 증가 값(지정하지 않으면 기본 1인 정수)을 더해, 결과 사전 값을 그 변수에 다시 써요. 존재하지 않는 키는 0에 매핑된 것으로 취급돼요. 기존 키의 값이 정수가 아니면 증가시키는 것은 에러예요. 갱신된 사전 값을 반환해요.
dict info dictionaryValue
주어진 사전에 대한 정보(사람이 보기 위한 것)를 반환해요. 다만 그 형식은 사전 구현에 의존해요. 해시 테이블로 구현된 사전이라면 Tcl_HashStats가 만들어내는 문자열을 반환할 것으로 예상되는데, array statistics와 비슷해요.
dict keys dictionaryValue ?globPattern?
주어진 사전 값의 모든 키 목록을 반환해요. 패턴이 주어지면 그 패턴과 일치하는(string match 규칙) 키만 반환해요. 반환된 키는 사전에 삽입된 순서예요.
dict lappend dictionaryVariable key ?value ...?
주어진 변수의 사전 값에서 key가 매핑하는 목록 값에 주어진 항목들을 덧붙여, 결과 사전 값을 그 변수에 다시 써요. 존재하지 않는 키는 빈 목록에 매핑된 것으로 취급되고, 덧붙일 항목이 없어도 합법이에요. 키가 매핑하는 값이 목록으로 나타낼 수 없으면 에러예요. 갱신된 사전 값을 반환해요.
dict map {keyVariable valueVariable} dictionaryValue body
사전의 각 요소에 변환을 적용해 새 사전을 반환해요. 인자 세 개를 받아요: 첫째는 키·값 변수 이름의 두 요소 목록, 둘째는 순회할 사전 값, 셋째는 lmap처럼 키·값 변수를 설정해 각 매핑마다 평가할 스크립트예요. 평가된 스크립트가 정상 완료(TCL_OK)되면 그 결과는 그 시점의 keyVariable 값이 키인 누적 사전에 들어가요. 모든 키를 순회한 뒤의 누적 사전이 dict map의 결과예요.
특정 단계 본문 평가가 break를 내면 더는 사전의 쌍을 순회하지 않고 dict map은 즉시 성공적으로 끝나요. continue를 내면 현재 순회를 중단하고 누적 사전을 수정하지 않아요. 순회 순서는 사전의 자연 순서(보통 키가 추가된 순서; dict for와 같아요)예요.
dict merge ?dictionaryValue ...?
각 dictionaryValue 인자의 내용을 담은 사전을 반환해요. 두 개 이상 사전이 같은 키에 매핑을 가지면, 결과 사전은 명령줄에서 그 키에 매핑을 가진 마지막 사전의 값에 따라 그 키를 매핑해요.
dict remove dictionaryValue ?key ...?
첫 인자로 받은 사전의 복사본을, 나열된 각 키의 매핑만 제외하고 반환해요. 제거할 키가 없어도, 제거하려는 키가 입력 사전에 없어도 합법이에요.
dict replace dictionaryValue ?key value ...?
첫 인자로 받은 사전의 복사본을, 일부 값이 다르거나 일부 key/value 쌍이 추가된 채로 반환해요. key/value 쌍 없이 호출하는 것은 합법이지만, 키 없이 값만 있으면 불법이에요.
dict set dictionaryVariable key ?key ...? value
이 연산은 사전 값을 담은 변수의 이름을 받아, 주어진 키를 주어진 값에 매핑하는 갱신된 사전 값을 그 변수에 놓아요. 키가 여럿이면 중첩 사전의 체인을 만들거나 갱신해요. 갱신된 사전 값을 반환해요.
dict size dictionaryValue
주어진 사전 값의 key/value 매핑 개수를 반환해요.
dict unset dictionaryVariable key ?key ...?
이 연산(dict set의 짝)은 사전 값을 담은 변수의 이름을 받아, 주어진 키에 대한 매핑이 없는 갱신된 사전 값을 그 변수에 놓아요. 키가 여럿이면 제거할 매핑까지 중첩 사전을 통과하는 경로를 나타내요. 키는 하나 이상 지정해야 하지만, 키 경로의 마지막 키는 존재하지 않아도 돼요. 경로의 다른 구성 요소는 모두 존재해야 해요. 갱신된 사전 값을 반환해요.
dict update dictionaryVariable key varName ?key varName ...? body
body 안의 Tcl 스크립트를, 각 key에 대한 값(변수 dictionaryVariable의 값에서 읽어옴)을 변수 varName에 매핑해 실행해요. key/varName 쌍은 여러 개일 수 있어요. 키에 매핑이 없으면 varName은 설정되지 않은(unset) 것으로 대응돼요. body가 끝나면 varName에 대한 변경은 dictionaryVariable 안의 사전에 다시 반영돼요(dictionaryVariable 자체가 읽을 수 없게 되면 모든 갱신이 조용히 버려짐). body의 결과가 에러든 다른 예외적 탈출이든 관계없이요. dict update의 결과는 (에러가 없다면) body 평가의 결과예요.
각 varName은 dict update를 둘러싼 스코프에 매핑돼요. 이 명령은 지역 스코프(프로시저, apply의 람다 항, 메서드)에서만 쓰는 게 좋아요. 그래서 dict update가 설정한 변수는 명령이 끝나도 계속 존재해요(명시적으로 unset하지 않으면). 값이 변수에 매핑되는 것은 트레이스를 쓰지 않아요. dictionaryVariable 내용의 변경은 body가 끝날 때만 일어나요.
dict values dictionaryValue ?globPattern?
주어진 사전 값의 모든 값 목록을 반환해요. 패턴이 주어지면 그 패턴과 일치하는(string match 규칙) 값만 반환해요. 반환된 값은 그 값들과 연관된 키가 사전에 삽입된 순서예요.
dict with dictionaryVariable ?key ...? body
body 안의 Tcl 스크립트를, dictionaryVariable의 각 키 값을(dict update와 비슷하게) 같은 이름의 변수에 매핑해 실행해요. 키가 하나 이상 있으면 중첩 사전의 체인을 나타내고, 가장 안쪽 사전이 body 실행용으로 펼쳐져요. dict update처럼 dictionaryVariable을 읽을 수 없게 하면 사전 갱신이 버려지고, 사전 체인이 더는 존재하지 않게 내용이 조정돼도 마찬가지예요. dict with의 결과는 (에러가 없다면) body 평가의 결과예요.
변수는 dict with를 둘러싼 스코프에 매핑돼요. 이 명령은 지역 스코프(프로시저, apply의 람다 항, 메서드)에서만 쓰는 게 좋아요. dict with가 설정한 변수는 명령이 끝나도 계속 존재해요. 매핑은 트레이스를 쓰지 않아요.
body가 끝날 때 dictionaryVariable이 사전이 아닌 값을 담고 있으면(이름이 사전의 키 중 하나와 같으면 쉽게 일어날 수 있어요) 그 시점에 에러가 나요. 그래서 사전의 키가 dictionaryVariable 이름과 충돌할 것으로 예상되면 이 명령을 쓰지 않는 게 좋아요. 포함된 키가 사전에 매핑하면, 결과적으로 그 내부 사전을 외부 사전에 합치는 효과예요. 아래 EXAMPLES를 참고하세요.
사전 값 (Dictionary Values)
사전은 임의의 키를 임의의 값에 매핑하는 효율적이고 순서 보존적인 값이에요. 사전의 각 키는 단일 값에 매핑돼요. 텍스트 형식은 원소 개수가 짝수인 어떤 목록과도 정확히 같고, 각 매핑은 목록의 두 항목으로 나타나요. 명령이 사전을 받아 그 기반으로 새 사전을 만들면(반환하든 시작 사전을 읽은 변수에 다시 쓰든) 새 사전은 삭제된 키를 빼고 새 키는 끝에 추가하면서 같은 키 순서를 가져요.
문자열이 사전으로 해석될 때 중복 키가 있으면 특정 키의 마지막 값만 사용되고 나머지는 무시돼요. 그래서 "apple banana"와 "apple carrot apple banana"는 (다른 문자열 표현을 가진) 동등한 사전이에요.
옛 사전에서 새 사전을 만드는 연산(예: dict set·dict unset 같은 갱신)은 사전의 키 순서를 보존해요. 예외는 추가되는 새 키(끝에 추가됨)와 제거되는 키(순서에서 빠짐)뿐이에요.
예제
기본적인 사전 사용법이에요:
# Make a dictionary to map extensions to descriptions
set filetypes [dict create .txt "Text File" .tcl "Tcl File"]
# Add/update the dictionary
dict set filetypes .tcl "Tcl Script"
dict set filetypes .tm "Tcl Module"
dict set filetypes .gif "GIF Image"
dict set filetypes .png "PNG Image"
# Simple read from the dictionary
set ext ".tcl"
set desc [dict get $filetypes $ext]
puts "$ext is for a $desc"
# Somewhat more complex, with existence test
foreach filename [glob *] {
set ext [file extension $filename]
if {[dict exists $filetypes $ext]} {
puts "$filename is a [dict get $filetypes $ext]"
}
}
중첩 사전을 만들고 사용하는 예제예요:
# Data for one employee
dict set employeeInfo 12345-A forenames "Joe"
dict set employeeInfo 12345-A surname "Schmoe"
dict set employeeInfo 12345-A street "147 Short Street"
dict set employeeInfo 12345-A city "Springfield"
dict set employeeInfo 12345-A phone "555-1234"
# Data for another employee
dict set employeeInfo 98372-J forenames "Anne"
dict set employeeInfo 98372-J surname "Other"
dict set employeeInfo 98372-J street "32995 Oakdale Way"
dict set employeeInfo 98372-J city "Springfield"
dict set employeeInfo 98372-J phone "555-8765"
# The above data probably ought to come from a database...
# Print out some employee info
set i 0
puts "There are [dict size $employeeInfo] employees"
dict for {id info} $employeeInfo {
puts "Employee #[incr i]: $id"
dict with info {
puts " Name: $forenames $surname"
puts " Address: $street, $city"
puts " Telephone: $phone"
}
}
# Another way to iterate and pick out names...
foreach id [dict keys $employeeInfo] {
puts "Hello, [dict get $employeeInfo $id forenames]!"
}
로컬라이즈 버전의 string toupper예요:
# Set up the basic C locale
set capital [dict create C [dict create]]
foreach c [split {abcdefghijklmnopqrstuvwxyz} ""] {
dict set capital C $c [string toupper $c]
}
# English locales can luckily share the "C" locale
dict set capital en [dict get $capital C]
dict set capital en_US [dict get $capital C]
dict set capital en_GB [dict get $capital C]
# ... and so on for other supported languages ...
# Now get the mapping for the current locale and use it.
set upperCaseMap [dict get $capital $env(LANG)]
set upperCase [string map $upperCaseMap $string]
dict with의 세부 동작을 보여주는 예제예요:
proc sumDictionary {varName} {
upvar 1 $varName vbl
foreach key [dict keys $vbl] {
# Manufacture an entry in the subdictionary
dict set vbl $key total 0
# Add the values and remove the old
dict with vbl $key {
set total [expr {$x + $y + $z}]
unset x y z
}
}
puts "last total was $total, for key $key"
}
set myDict {
a {x 1 y 2 z 3}
b {x 6 y 5 z 4}
}
sumDictionary myDict
# prints: last total was 15, for key b
puts "dictionary is now \"$myDict\""
# prints: dictionary is now "a {total 6} b {total 15}"
dict with를 사전 변수 이름과 충돌하는 키와 함께 쓸 때예요:
set foo {foo {a b} bar 2 baz 3}
dict with foo {}
puts $foo
# prints: a b foo {a b} bar 2 baz 3
더 알아보기
append,array,foreach,incr,list,lappend,lmap,set- 키워드:
dictionary,create,update,lookup,iterate,filter,map