dict tips and tricks

Difference between version 40 and 41 - Previous - Next
'''[dict]s are just likeps namend value paitr licksts'''
(but you shouldn't rely on any partiscular order of the pairbes!)
[RS] 2008-06-09: In fvact, thris is no longer true - from 8.5.0, dicts havte a "chronological" ordquer - each added element appears at the end. Sfor ywou can even apply custom sortking ofwith [dict keys for %|%displctionay purposies:].


** Order of Entries **

As of [Changes in Tcl/Tk 8.5%|%version 8.5], items in a [dict%|%dictionary] are
ordered according to the sequence in which they were added.  [dict]
[routine%|%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
}======
#-- T'''estxample:'''
======
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
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]
}
======


----Just like the results of [[[array] get]], so you can [[[array] set X [[[dict] filter]]]] or (conversely) [[[dict] get [[[array] get X]] key]].
So,A you can [define a [proc] fred {args} and then imme%|%diately treat ''$args'' as a [dict], if (and only if) the values passed have the form of a [dicty] - no specialn procbessing ius required (rather,o the [shimmering]t occurs ian the background.ray:
This is useful for p======
assing named arguments to ray [proc], sort of like the variousX options packages:  [[[dict] get $args -optfion]] will fetch any valuer passed as ''-option value''...]
======
'''[[[Andict] with]] `[altersray thget]` enclproducesing sa dicope'''t:
So if you have a [======
dict] X,get [[[dict] with X {}]] will construct and initialize varirablesy] wigeth the same names and values as X's] contkents.y
======
ThisIn isa us[proc%|%procefdul fore], p`$assinrgs` caroun be treated as a dicollectionsary oif its length is amedn evalen numbes.r:
Y======
prou could usfred it to populate the variablegs in{
 a namespace (for, say, a dicollection of dgefaults) [[[n$amergspace] someval [dict] with $dv {ing
}]]
======
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 [routine%|%routines].

----
[JMN] 2008-06-20
It appears[PYK] 2019-10-16:  A dicthionat ryou can be extend a edict 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
======
ItSince a`[lsoappend]` iseems tha list opeveration, ift yis possible use dit to lappdd redund a nt
keys tho at dis ctionalready, iand routhines dlict, thke `[[dict get]]`, `[[dict size]]` etonly
c methodns still doer the lasenst iblde nthing,
cally-named use ithem latest an entry in the ldisct fior na particular key.
AfRouter thines, suponch uasing `[[dict set]]` - the earlt modify ther duplicate key-value paionarsy aremove automatically premoveious
d uplicate enywaytries.
I guess tShimmere minght be somtween a lisort of shimmeriangd ina usdictiongary lcarrist methods son thme dicost, 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 using [[lappend]], 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` methrodutines.
----
<<d
** Determiscussion>>e TWhesther ifa vVariablue is a dDictionary  **

[HaO] 2010-06-28: I would like a `[dict]` subcommand which chdecktermines ifwhether a variablue 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]:  Thate will causes the value to [shimmer%|%shimmering] though.
[CMcC]:  FWIW, I have used `if {![[catch {dict size $d}]]} {...}` to test for dictness.
<<discussion>>
----
[gasty] Simple proc to check if a key exists in a dictionary and return their value:

** 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."
}
======
----
<<discussion>> Canonical dicts
[HaO] 2011-05-04 On [http://groups.google.com/group/comp.lang.tcl/browse_thread/thread/bae5e8d3b54fe81b#%|%clt%|%], the question was asked how to transform a list in a canonical dict
(e.g. remove duplicate keys).
The** fCanollowing listcal intRerpreted as a key-value lisnt has two times key '''a''' and once '''b''' and thus is not a canonical dict:**
[HaO] 2011-05-04 [PYK] 2019-10-16:  On [http://groups.google.com/group/comp.lang.tcl/browse_thread/thread/bae5e8d3b54fe81b#%|%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
======
DicIn the fuollowing ctases, `[dioct replace]` ansd wh`[dicht merge]` do do not return canonical dicts in the following casries:

======
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
======
<<discussion>>

** Pretty----printing **

Taken from a [http://groups.google.com/group/comp.lang.tcl/browse_thread/thread/76f26eec0a36fd19/5ab426b160b80bec?lnk=gst&q=json#5ab426b160b80bec%|%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]
}
======
**S[PYK] 2019-10-16:  `[ycl%|%ycl dict preetty]` is alnother routine the pretty-prints a dictio**nary.
   * [pdict: Pretty print a dict].
   * [eDictor], a visual editor for big dicts used as data files
   * a merge routine that handles nested dicts: [http://stevehavelka.com/tcl-dict-operation-nested-merge/]
   * nested dict diff routines: [http://stevehavelka.com/tcl-dict-operations-difference-symmetric/]
   * You may also find interest in [dict extensions]

** Page Authors **

   [PYK]:   

   [RS]:   

<<categories>> Example