dict lappend

dict lappend appends items to a list in a dictionary.

Synopsis

dict lappend dictionaryName key ?value ...?

Description

Each value is appended to the list named by key in the dictionary named by dictionaryName. If key does not exist, it is created. It is legal to provide no value arguments. The dictionary is modified in-place, and it is also returned.

dict lappend for deeper nested keys

HaO 2015-12-15: I would love to have a native command which does an lappend to a subdict. E.g.:

% set d {key1 {key2 value1}}
% dictlappendsub d key1 key2 value2
key1 {key2 {value1 value2}}
% dictlappendsub d key1 key3 value3
key1 {key2 {value1 value2} key3 value3}

This might be achieved by:

# dictlappendsub dict key1 ... keyn value
proc dictlappendsub {dictName args} {
    upvar 1 $dictName d
    set keys [lrange $args 0 end-1]
    if {[dict exists $d {*}$keys]} {
        dict set d {*}$keys [linsert [dict get $d {*}$keys] end [lindex $args end]]
    } else {
        dict set d {*}$keys [lrange $args end end]
    }
}

PYK 2015-12-15: dict update could be put to use here:

set d {key1 {key2 value1}}
dict update d key1 key1 {
    dict lappend key1 key2 value2
    dict lappend key1 key3 value3
} ;# -> key2 {value1 value2} key3 value3
set d ;# -> key1 {key2 {value1 value2} key3 value3}

dict with could have been used to similar effect, but then there would be a risk of variable name collision.

A corresponding dictlappendsub, AKA dictnlappend, could be implemented like this:

proc dictnlappend {name keys args} {
    upvar 1 $name d
    set keys [lassign $keys[set keys {}] key]
    if {[llength $keys]} {
        dict update d $key key [list dictnlappend key $keys {*}$args] 
    } else {
        dict lappend d $key $args
    }
    return $d
}

And just for fun, below is a variant that employs code generation. For deeply-nested dictionaries, bump up interp recursionlimit:

proc dictnlappend {name keys args} {
    upvar 1 $name d0
    set i [expr {[llength $keys]} - 1]
    set keys [lassign [lreverse $keys[set keys {}]] keyn]
    set body "dict lappend d$i [list $keyn] {*}\$args"
    foreach key $keys {
        set body [lreverse [list $body d$i $key d[incr i -1] update dict]]
    }
    if 1 $body
    return $d0
}

Thinking about a dict with approach again, substitution can be used to pass literal values into the body of dict with to avoid a collision with the name "args", and a local name that doesn't collide with a key in the opened dictionary can be chosen for the main dictionary variable. This variant avoids recursion, and passes a pure list as the body of dict with, which saves the interpreter the trouble of parsing the body as a string:

proc dictnlappend {dictname keys args} {
    upvar 1 $dictname d
    if {![llength $args]} return 
    if {![dict exists $d {*}$keys]} {
        dict set d {*}$keys {}
    }
    set keys1 [lrange $keys 0 end-2] 
    while {[dict exists $d {*}$keys1 d[incr i]]} {}
    upvar 1 $dictname d$i
    dict with d$i [list dict lappend {*}[lrange $keys end-1 end] {*}$args]
    set d$i
}