Updated 2016-02-23 16:49:14 by Napier

dicts are just like name value pair lists (but you shouldn't rely on any particular order of the pairs!) RS 2008-06-09: In fact, this is no longer true - from 8.5.0, dicts have a "chronological" order - each added element appears at the end. So you can even apply custom sorting of dict keys for display purposes:
proc dict'sort {dict args} {
    set res {}
    foreach key [lsort {*}$args [dict keys $dict]] {
        dict set res $key [dict get $dict $key] 
    }
    set res
}

#-- Test:

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 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]
}

Just like the results of [array get], so you can [array set X [dict filter]] or (conversely) [dict get [array get X] key].

So, you can define a proc fred {args} and then immediately treat $args as a dict, if (and only if) the values passed have the form of a dict - no special processing is required (rather, the shimmering occurs in the background.

This is useful for passing named arguments to a proc, sort of like the various options packages: [dict get $args -option] will fetch any value passed as -option value.

[dict with] alters the enclosing scope

So if you have a dict X, [dict with X {}] will construct and initialize variables with the same names and values as X's contents.

This is useful for passing around collections of named values.

You could use it to populate the variables in a namespace (for, say, a collection of defaults) [namespace eval dict with $dv {}]

JMN 2008-06-20 It appears that you can extend a dict using lappend. For the case of a loop where you know the newly added keys are not currently in the dict - might this be faster than using dict set? e.g
foreach val $newValues {
    lappend mydict [uuid::uuid generate] $val
}

or
lappend mydict {*}$newPairs

It also seems that even if you do lappend a key that is already in the dict, the [dict get], [dict size] etc methods still do the sensible thing, and use the latest entry in the list for a particular key. After this, upon using [dict set] - the earlier duplicate key-value pairs are automatically removed anyway.

I guess there might be some sort of shimmering in using list methods on the dict, 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 dict is converted back to a proper dict 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 $newPairs]

update: A few rough tests indicate that the lappend method is actually slower. The foreach loop does indeed run faster using [lappend], than [dict set] - but this time (and more!) is lost during the subsequent access of the value as a dict 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 dict, just build it as a dict using the dict methods.

  Test if variable is a dict

HaO 2010-06-28 I would like a dict subcommand which checks if a variable is a dict 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 That will cause shimmering though.

CMcC FWIW, I have used if {![catch {dict size $d}]} {...} to test for dictness.


gasty Simple proc to check if a key exists in a dictionary and return their value:
proc getDictItem {dictVal keyVar} {
        upvar $keyVar keyVal
        if {[dict exist $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 dicts

HaO 2011-05-04 On clt, the question was asked how to transform a list in a canonical dict (e.g. remove duplicate keys).

The following list interpreted as a key-value list has two times key a and once b and thus is not a canonical dict:
% set l {a 1 b 2 a 3}

Methods to transform the list in a canonical dict:
% dict create {*}$l
% dict merge $l $l
a 3 b 2

Dict functions which do not return canonical dicts in the following cases:
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 dict. 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.

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


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]
}

See also edit