dict tips and tricks

dict tips and tricks describes various techniques for working with dictionaries.

Order of Entries

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 2

This 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.

dict with Sets Variables at the Current Level

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 {*}$newPairs

Since 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.

Determine Whether a Value is a Dictionary

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.

Set A Variable by Key Name

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."
}

Canonical Representation

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 2

In 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
2

AMG: 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 2

Pretty-printing

Taken 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.

Page Authors

PYK
RS