dict(3tcl)

πŸ’– NAME πŸ’–

dict β€” manipulate dictionaries, and honestly? lowkey the most useful Tcl command you'll ever meet, no cap 🫢


πŸ¦‹ SYNOPSIS πŸ¦‹

dict option arg ?arg ...?

🌸 DESCRIPTION 🌸

Okay bestie, so dict does one of several operations on dictionary values, or on variables that hold dictionary values (peep the DICTIONARY VALUES section further down for the whole explanation of what a dictionary even is), and which operation you get depends on option. πŸ’…

The legal options are here β€” and heads up, they may be abbreviated (so yes you can be lazy, we love that for you):

  • dict append dictionaryVariable key ?string ...? β€” this takes the string (or strings, plural, she's flexible ✨) you handed it and appends them onto the value that key maps to, inside the dictionary value sitting in dictionaryVariable. Then it writes the resulting dictionary value back into that variable. Keys that don't exist yet? Treated like they map to an empty string, nbd. The updated dictionary value gets returned. πŸ’– If dictionaryVariable indicates an element that does not exist of an array that has a default value set, the default value will be used as the value of the dictionary prior to the appending operation.
  • dict create ?key value ...? β€” returns a brand-new dictionary holding each of the key/value mappings you listed as arguments. Keys and values alternate, with each key followed by its associated value. Simple, iconic, slay. πŸŽ€
  • dict exists dictionaryValue key ?key ...? β€” gives you back a boolean telling you whether the given key (or a whole path of keys threaded through a set of nested dictionaries) exists inside dictionaryValue. It returns a true value exactly when dict get on that path would succeed. Basically it's the vibe check before you commit. πŸ‘‘
  • dict filter dictionaryValue filterType arg ?arg ...? β€” takes a dictionary value and returns a shiny new dictionary containing just those key/value pairs that match the filter type you picked (and yes, filterType may be abbreviated). The supported filter types are:
    • dict filter dictionaryValue key ?globPattern ...? β€” the key rule only matches key/value pairs whose keys match any of the given patterns (in the style of string match). 🌸
    • dict filter dictionaryValue script {keyVariable valueVariable} script β€” the script rule tests for matching by assigning the key to keyVariable and the value to valueVariable, then evaluating the given script, which should result in a boolean value. The key/value pair only makes it into the result of dict filter when a true value comes back. πŸ“ Note that the first argument after the rule selection word is a two-element list. If the script returns with a condition of TCL_BREAK, no further key/value pairs are considered for inclusion in the resulting dictionary, and a condition of TCL_CONTINUE is equivalent to a false result. The key/value pairs are tested in the order in which the keys were inserted into the dictionary.
    • dict filter dictionaryValue value ?globPattern ...? β€” the value rule only matches key/value pairs whose values match any of the given patterns (again, in the style of string match). ✨
  • dict for {keyVariable valueVariable} dictionaryValue body β€” three arguments, bestie, keep up: (1) a two-element list of variable names, for the key and value respectively of each mapping in the dictionary, (2) the dictionary value you're iterating across, and (3) a script that gets evaluated once per mapping with the key and value variables set appropriately (in the manner of foreach). The result of the command is an empty string. If any evaluation of the body generates a TCL_BREAK result, no further pairs from the dictionary get iterated over and the dict for command terminates successfully immediately. If any evaluation of the body generates a TCL_CONTINUE result, that's treated exactly like a normal TCL_OK result. The order of iteration is the order in which the keys were inserted into the dictionary. πŸ”₯
  • dict get dictionaryValue ?key ...? β€” hand it a dictionary value (first argument) and a key (second argument) and it retrieves the value for that key. When you supply several keys, the behaviour is as if the result of dict get $dictVal $key was passed as the first argument to dict get with the remaining arguments as second (and possibly subsequent) arguments. That's what makes lookups in nested dictionaries so easy. For example, these two commands are equivalent:
dict get $dict foo bar spong
dict get [dict get [dict get $dict foo] bar] spong

If you provide no keys, dict get returns a list containing pairs of elements in a manner similar to array get β€” that is, the first element of each pair is the key and the second element is the value for that key. πŸ’œ

It is an error to attempt to retrieve a value for a key that is not present in the dictionary. πŸ’€
  • dict getdef dictionaryValue ?key ...? key default
  • dict getwithdefault dictionaryValue ?key ...? key default

This behaves the same as dict get (with at least one key argument), returning the value that the key path maps to in the dictionary dictionaryValue β€” except that instead of producing an error because the key (or one of the keys on the key path) is absent, it returns the default argument instead. 🫢

Note that there must always be at least one key provided, and that dict getdef and dict getwithdefault are aliases for each other.

  • dict incr dictionaryVariable key ?increment? β€” adds the given increment value (an integer that defaults to 1 if you don't specify one) to the value that key maps to in the dictionary value contained in dictionaryVariable, then writes the resulting dictionary value back to that variable. Keys that don't exist are treated as if they map to 0. It is an error to increment a value for an existing key if that value is not an integer. The updated dictionary value is returned. πŸ’– If dictionaryVariable indicates an element that does not exist of an array that has a default value set, the default value will be used as the value of the dictionary prior to the incrementing operation.
  • dict info dictionaryValue β€” returns information about the given dictionary, intended for display to people, though the format of this data depends on the implementation of the dictionary. For dictionaries implemented by hash tables, it is expected that this will return the string produced by Tcl_HashStats, similar to array statistics. πŸ‘€
  • dict keys dictionaryValue ?globPattern? β€” returns a list of all keys in the given dictionary value. If you supply a pattern, only those keys that match it (according to the rules of string match) get returned. The returned keys will be in the order that they were inserted into the dictionary. 🌈
  • dict lappend dictionaryVariable key ?value ...? β€” appends the given items to the list value that key maps to in the dictionary value contained in dictionaryVariable, writing the resulting dictionary value back to that variable. Keys that don't exist are treated as if they map to an empty list, and it is legal for there to be no items to append to the list. It is an error for the value that the key maps to to not be representable as a list. The updated dictionary value is returned. πŸŽ€ If dictionaryVariable indicates an element that does not exist of an array that has a default value set, the default value will be used as the value of the dictionary prior to the list-appending operation.
  • dict map {keyVariable valueVariable} dictionaryValue body β€” applies a transformation to each element of a dictionary and returns a new dictionary. Three arguments: (1) a two-element list of variable names, for the key and value respectively of each mapping in the dictionary, (2) the dictionary value you're iterating across, and (3) a script evaluated for each mapping with the key and value variables set appropriately (in the manner of lmap). In an iteration where the evaluated script completes normally (TCL_OK, as opposed to an error, etc.) the result of the script is put into an accumulator dictionary using the key that is the current contents of the keyVariable variable at that point. The result of the dict map command is the accumulator dictionary after all keys have been iterated over. ⭐

If the evaluation of the body for any particular step generates a break, no further pairs from the dictionary will be iterated over and the dict map command will terminate successfully immediately. If the evaluation of the body for a particular step generates a continue result, the current iteration is aborted and the accumulator dictionary is not modified. The order of iteration is the natural order of the dictionary (typically the order in which the keys were added to the dictionary; the order is the same as that used in dict for).

  • dict merge ?dictionaryValue ...? β€” returns a dictionary containing the contents of each of the dictionaryValue arguments. Where two (or more) dictionaries contain a mapping for the same key, the resulting dictionary maps that key to the value according to the last dictionary on the command line containing a mapping for that key. Winner takes the key, periodt. πŸ‘‘
  • dict remove dictionaryValue ?key ...? β€” returns a new dictionary that's a copy of the old one you passed in as the first argument, except without mappings for each of the keys listed. It is legal for there to be no keys to remove, and it is also legal for any of the keys to be removed to not be present in the input dictionary in the first place. 🧚
  • dict replace dictionaryValue ?key value ...? β€” ok bestie, this one returns a brand new dictionary that's a copy of the old one you passed in as the first argument, except some values are different or you added some extra key/value pairs. It's totally legal to call this with no key/value pairs at all, but it is illegal to call it with a key but no value. So don't do that. πŸ’…
  • dict set dictionaryVariable key ?key ...? value β€” this operation takes the name of a variable that holds a dictionary value and shoves an updated dictionary value into that variable, with a mapping from the given key to the given value. When you give multiple keys, it creates or updates a chain of nested dictionaries. The updated dictionary value gets returned. And if dictionaryVariable points to an element that doesn't exist of an array that has a default value set, that default value will be used as the value of the dictionary before the value insert/update operation. (So it's like a little pre-fill situation.) ✨
  • dict size dictionaryValue β€” returns the number of key/value mappings in the given dictionary value. Simple. Count 'em up. πŸ”’
  • dict unset dictionaryVariable key ?key ...? β€” this is the companion to dict set. It takes the name of a variable with a dictionary value and puts an updated dictionary value in that variable that does not contain a mapping for the given key. When you give multiple keys, it describes a path through nested dictionaries to the mapping you want to remove. At least one key must be specified, but the last key on the key-path doesn't need to exist. All the other components on the path must exist. The updated dictionary value is returned. And if dictionaryVariable points to an element that doesn't exist of an array that has a default value set, that default value will be used as the value of the dictionary before the value remove operation. πŸ—‘οΈ
  • dict update dictionaryVariable key varName ?key varName ...? body β€” executes the Tcl script in body with the value for each key (as found by reading the dictionary value in dictionaryVariable) mapped to the variable varName. You can have multiple key/varName pairs. If a key doesn't have a mapping, that corresponds to an unset varName. When body terminates, any changes you made to the varNames get reflected back to the dictionary inside dictionaryVariable (unless dictionaryVariable itself becomes unreadable, in which case all updates are silently discarded), even if the result of body is an error or some other kind of exceptional exit. The result of dict update is (unless some kind of error occurs) the result of evaluating body. And if dictionaryVariable points to an element that doesn't exist of an array that has a default value set, that default value will be used as the value of the dictionary before the update operation. πŸ’–

Each varName is mapped in the scope enclosing the dict update; it's recommended that you only use this command in a local scope (procedure, lambda term for apply, or method). Because of this, the variables set by dict update will continue to exist after the command finishes (unless you explicitly unset them). So like, they don't just vanish. πŸ‘»

Note that the mapping of values to variables does not use traces; changes to the dictionaryVariable's contents only happen when body terminates. So no sneaky updates mid-script. 🚫

  • dict values dictionaryValue ?globPattern? β€” returns a list of all values in the given dictionary value. If you supply a pattern, only those values that match it (according to the rules of string match) will be returned. The returned values will be in the order that the keys associated with those values were inserted into the dictionary. So it's like a little ordered parade of values. πŸŽ‰
  • dict with dictionaryVariable ?key ...? body β€” executes the Tcl script in body with the value for each key in dictionaryVariable mapped (similar to dict update) to a variable with the same name. When you give one or more keys, they indicate a chain of nested dictionaries, with the innermost dictionary being the one opened out for the execution of body. As with dict update, making dictionaryVariable unreadable will make the updates to the dictionary be discarded, and this also happens if the contents of dictionaryVariable are adjusted so that the chain of dictionaries no longer exists. The result of dict with is (unless some kind of error occurs) the result of evaluating body. If dictionaryVariable points to an element that doesn't exist of an array that has a default value set, that default value will be used as the value of the dictionary before the updating operation. 🌸

The variables are mapped in the scope enclosing the dict with; it's recommended that you only use this command in a local scope (procedure, lambda term for apply, or method). Because of this, the variables set by dict with will continue to exist after the command finishes (unless you explicitly unset them). Same vibe as dict update. πŸ’œ

Note that the mapping of values to variables does not use traces; changes to the dictionaryVariable's contents only happen when body terminates. Again, no mid-script updates. βœ‹

If the dictionaryVariable contains a value that is not a dictionary at the point when the body terminates (which can easily happen if the name is the same as any of the keys in the dictionary) then an error occurs at that point. This command is thus not recommended for use when the keys in the dictionary are expected to clash with the dictionaryVariable name itself. Where the contained key does map to a dictionary, the net effect is to combine that inner dictionary into the outer dictionary; see the EXAMPLES below for an illustration of this. So like, don't name your variable the same as a key, or things get messy. πŸ’€

πŸ’– DICTIONARY VALUES πŸ’–

Dictionaries are values that contain an efficient, order-preserving mapping from arbitrary keys to arbitrary values. Each key in the dictionary maps to a single value. They have a textual format that is exactly that of any list with an even number of elements, with each mapping in the dictionary being represented as two items in the list. So it's like a list, but fancy. ✨

When a command takes a dictionary and produces a new dictionary based on it (either returning it or writing it back into the variable that the starting dictionary was read from) the new dictionary will have the same order of keys, modulo any deleted keys and with new keys added on to the end. So order is preserved, except deletions and additions at the end. πŸ“

When a string is interpreted as a dictionary and it would otherwise have duplicate keys, only the last value for a particular key is used; the others are ignored, meaning that, β€œapple banana” and β€œapple carrot apple banana” are equivalent dictionaries (with different string representations). So like, if you have dupes, the last one wins. 🍎🍌

Operations that derive a new dictionary from an old one (e.g., updates like dict set and dict unset) preserve the order of keys in the dictionary. The exceptions to this are for any new keys they add, which are appended to the sequence, and any keys that are removed, which are excised from the order. So order is mostly kept, except for new keys and removed keys. πŸ—‚οΈ

πŸ’– EXAMPLES πŸ’–

Okay bestie, this is the part where it all clicks. Grab a snack, we're doing real code. ✨


🌸 Basic dictionary usage 🌸

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

See how it's just vibes? Make a dict, dict set to add or update, dict get to read, dict exists to check before you commit. That's the whole personality. πŸ’…


πŸ¦‹ Constructing and using nested dictionaries πŸ¦‹

Nested dicts are lowkey the main character here β€” dicts inside dicts inside… it's giving infinity scarf. 🧣

# 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]!"
}

Two ways to walk it, both iconic: dict for to loop the pairs, and dict with to just unpack the keys into variables so you can talk to them by name. And yeah, the comment is right β€” that data probably should come from a database, but that's a whole other manpage, bestie. 😭


πŸŽ€ A localizable version of string toupper πŸŽ€

Yes queen, build your own uppercase map, per-locale, from scratch:

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

Notice $env(LANG) β€” we're pulling the current locale straight out of the environment. Environment variables ate. πŸ‘‘


πŸ“ Showing the detail of dict with πŸ“

This one is the deep dive. Watch what dict with does to a subdictionary when it hands you the keys, and what happens to the subdictionary when you unset them:

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

Like. The way it rewrites the dictionary in place as it goes. That's the whole trick β€” the x, y, z keys get swapped out for total. Obsessed. πŸ’–


πŸ’œ When dict with meets a key that clashes πŸ’œ

And here's the plot twist, because of course there's one. If you use dict with with a key that has the same name as the dictionary variable itself, the variable you're holding onto gets rebound to the subdictionary β€” and when it's written back, things can get weird. Look:

set foo {foo {a b} bar 2 baz 3}
dict with foo {}
puts $foo
#    prints: a b foo {a b} bar 2 baz 3

Not the foo {a b} bar 2 baz 3 you started with, right? Because the foo inside got replaced by the value a b during the write-back. So like, name your variables carefully, bestie. This is a watch-your-step moment, not a "trust the vibes" moment. πŸ™ˆ


🌈 SEE ALSO 🌈

The girlies in the extended universe: append(3tcl), array(3tcl), foreach(3tcl), incr(3tcl), list(3tcl), lappend(3tcl), lmap(3tcl), set(3tcl)


⭐ KEYWORDS ⭐

dictionary, create, update, lookup, iterate, filter, map


And that's the whole page, babe. 🫢 Straight from Tcl 8.5, dict(3tcl), printed with love. Go forth and manipulate dictionaries like the icon you are. Periodt. πŸ’…βœ¨πŸŽ€

πŸ“¦ where this page comes from