dict tips and tricks describes various techniques for working with dictionaries.
As of version 8.5, items in a dictionary are ordered according to the sequence in which they were added. dict routines return results according to that order:
proc dict'sort {dict args} {
set res {}
foreach key [lsort {*}$args [dict keys $dict]] {
dict set res $key [dict get $dict $key]
}
set res
}example:
set d1 {foo 1 bar 2 grill 3}
puts 1:[dict'sort $d1] ;# 1:bar 2 foo 1 grill 3
puts 2:[dict'sort $d1 -decreasing] ;# 2:grill 3 foo 1 bar 2This works for me correctly for Tcl 8.4 version posted by kruzalex
sugar::proc dict'sort {dict args} {
set res {}
foreach key [lsort {*}$args [dict keys $dict]] {
lappend a() $key [dict get $dict $key]
}
set res [lindex [array get a] 1]
}or without sugar::proc
proc dict'sort {dict args} {
set res {}
foreach key [eval [list lsort] [lrange $args 0 end] [list [dict keys $dict]]] {
lappend a() $key [dict get $dict $key]
}
set res [lindex [array get a] 1]
}A dictionary can be used to set an array:
array set X [[dict] filter ...]
And array get produces a dict:
dict get [[array] get X] key
In a procedure, $args can be treated as a dictionary if its length is an even number:
proc fred args {
dict get $args something
}A simple way of dealing with options is to treat $args as a dictionary:
# returns an error if $args is not a dictionary
dict size $args
foreach {opt val} $args {
switch $opt {
someoption {
...
}
someotheroption {
...
}
}
}See Command Options for further information on the topic.
Consider :
set x {one 1 two 2}
dict with x {}
puts [list $one $two]dict with assigned all the items in $x to variables. This is convenient for passing named values between routines.
JMN 2008-06-20 PYK 2019-10-16: A dictionary can be extended using lappend. For the case of a loop where you know the newly added keys are not currently in the dictionary - might this be faster than using dict set? e.g
foreach val $newValues {
lappend mydict [uuid::uuid generate] $val
}or
lappend mydict {*}$newPairsSince lappend is a list operation, it is possible use it to add redundant keys to a dictionary, and routines like dict get, dict size only consider the last identically-named item as an entry in the dictionary. Routines such as dict set that modify the dictionary remove all previous duplicate entries.
Shimmering between a list and a dictionary carries some cost, but presumably in the above case the lappend would still be a win for large datasets because the existence of the key doesn't need to be checked each time a new value is added. Perhaps this gain is lost anyway once the dictionary is converted back to a proper dictionary value.
I've not had a chance to test the relative performance of this yet... so don't consider it as a tip/trick til you've verified it helps for your particular case!
In particular - it might be worth comparing the above with:
set mydict [dict merge $mydict[set mydict {}] $newPairs]update: A few rough tests indicate that the lappend method is actually slower. The foreach loop using lappend does indeed run faster than dict set - but this time (and more!) is lost during the subsequent access of the value as a dictionary using dict size $mydict
For Tcl8.6a0 at least - it would seem the moral is, if you're going to be using it as a dictionary, just build it as a dictionary using the dict routines.
HaO 2010-06-28: I would like a dict subcommand which determines whether a value is a dictionary similar to array exists. The pdict example uses:
if { [catch {dict keys ${d}}] } {
error "error: pdict - argument is not a dict"
}which is ok but might pollute the error log as a side effect. Is there a more elegant solution ?
AMG: You can use [string is list] plus a test for even [llength].
if {![string is list $d] || ([llength $d] & 1)} {
error "not a dict"
}APN: The causes the value to shimmering though.
CMcC: FWIW, I have used if {![catch {dict size $d}]} {...} to test for dictness.
gasty: Simple procedure to check if a key exists in a dictionary and return their value:
proc getDictItem {dictVal keyVar} {
upvar $keyVar keyVal
if {[dict exists $dictVal $keyVar]} {
set keyVal [dict get $dictVal $keyVar]
return 1
}
return 0
}
# demo
set d [dict create a 1 b 2 c 3]
puts "dict value = $d"
if {[getDictItem $d a]} {
puts "key 'a' exists in dict. a=$a"
}
if {![getDictItem $d z]} {
puts "key 'z' not exists in dict."
}HaO 2011-05-04 PYK 2019-10-16: On clt , the question was asked how to transform a list in a canonical dictionary (e.g. removing duplicate keys and normalizing the representation of each key and value).
In the following dictionary, there are two entries named a and one entry named b, so it is not in canonical form:
% set l {a 1 b 2 a 3}Methods to transform the list into a canonical dictionary:
% dict create {*}$l
% dict merge $l $l
a 3 b 2In the following cases, dict replace and dict merge do do not return canonical dictionaries:
dict replace $l dict merge $1 a 1 b 2 a 3
To resume, no method was found to directly transform a list in a canonical dictionary. There is always a small "derivation".
Within the thread, it was proposed to define dict replace $l as such a function, which is quite similar to lrange $l 0 end, which forms a canonical list.
PYK 2019-10-16: In current versions of Tcl dict replace does indeed return a new dictionary, meaning that redundant keys have been discarded and that the string representation of each value has been normalized.
Functions, which do a canonicalization:
% dict for {k v} $l {puts -nonewline "$k $v "} ; puts ""
a 3 b 2
% dict size $l
2AMG: Actually, this is incredibly easy to do. Just call [dict get $dictValue] with no additional arguments.
% dict get {a 1 b 2 a 3}
a 3 b 2Taken from a posting on comp.lang.tcl , this is code for pretty-printing a dict:
namespace eval DictUnsupported {
package require Tcl 8.6
#########################
## dict format dict
#
# convert dictionary value dict into string
# hereby insert newlines and spaces to make
# a nicely formatted ascii output
# The output is a valid dict and can be read/used
# just like the original dict
#############################
proc dict_format {dict} {
dictformat_rec $dict "" "\t"
}
proc isdict {v} {
string match "value is a dict *" [::tcl::unsupported::representation $v]
}
## helper function - do the real work recursively
# use accumulator for indentation
proc dictformat_rec {dict indent indentstring} {
# unpack this dimension
dict for {key value} $dict {
if {[isdict $value]} {
append result "$indent[list $key]\n$indent\{\n"
append result "[dictformat_rec $value "$indentstring$indent" $indentstring]\n"
append result "$indent\}\n"
} else {
append result "$indent[list $key] [list $value]\n"
}
}
return $result
}
namespace ensemble configure dict -map \
[linsert [namespace ensemble configure dict -map] end format [namespace current]::dict_format]
}PYK 2019-10-16: ycl dict pretty is another routine the pretty-prints a dictionary.
You wish to implement procedures, but not wishing to source..
# Create a dictionary and store the procedure
dict set myProcDict display_hello "proc display_hello {} { puts \"Hello\" }"
# Prepare a call back
proc get_dict_callback {callback} { uplevel #0 $callback }
# This will sequence the display_hello procedure
get_dict_callback [dict get $myProcDict display_hello]
% display_hello
Hello