TCL multi level dictionary - how to update same key by appending to it

Viewed 791

I have a dict called test, I wish to iteratively update the same key by appending new items to it. So eventually it would look like this

AIM

test {
1 {flps {o1 o2 o3 ...}}
}

What is the standard way to do this ? I have the below code right now:

set test [dict create] ;
dict set test 1 "flps" "o1"   ;  #### first value o1 added
set new "[dict get $test 1 regs] o2" ; ##temp variable that append the old + new value
dict set test 1 regs $new         ; ### does dict set overwrite ? 
3 Answers

What is the standard way to do this ?

There is no built-in command for this purpose, one approach is to use dict update:

% set test [dict create]
% dict update test 1 1 { dict lappend 1 flps "o1" }
flps o1
% set test
1 {flps o1}
% dict update test 1 1 { dict lappend 1 flps "o2" }
flps {o1 o2}
% set test
1 {flps {o1 o2}}

However, this easily becomes unhandy when there are more levels of nesting or even an unknown nesting depth.

Based on mrcalvin 's link- found this to work

% 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]} {    ; **## What does this do!? WOW**
        dict set d {*}$keys [linsert [dict get $d {*}$keys] end [lindex $args end]]
    } else {
        dict set d {*}$keys [lrange $args end end]
    }
}

There is no standard command for doing this because the number of possible commands for all the different things that might be done gets too large; only common cases are provided for you (with the exact choice depending in part on what is technically unambiguous given that Tcl's dictionaries allow arbitrary values as keys by design). Instead, dict update and dict with are provided as tools to allow people to make their own solutions.

In particular, by making the assumption that we are appending exactly one list item and with dict update, upvar and some recursion, we can make this:

proc dict_lappend_item {dictVar args} {
    upvar 1 $dictVar d
    if {[llength $args] == 1} {
        lappend d [lindex $args 0]
    } else {
        set args [lassign $args key]
        dict update d $key inner {
            dict_lappend_item inner {*}$args
        }
    }
    return $d
}

Here's using it:

% set test [dict create]
% dict set test 1 "flps" "o1"
1 {flps o1}
% dict_lappend_item test 1 flps o2
1 {flps {o1 o2}}
% dict_lappend_item test 1 flps o3
1 {flps {o1 o2 o3}}

That looks like it is doing the thing you need. (Note that dict lappend doesn't make that assumption; it instead handles the append-multiple-items case, which has some true performance nasties if not treated specially in C code.)


You can plug the above procedure into dict.

set map [namespace ensemble configure dict -map]
dict set map additem ::dict_lappend_item
namespace ensemble configure dict -map $map

and then you can do:

% dict additem test 1 flps o4
1 {flps {o1 o2 o3 o4}}

Careful if you do this. Overriding standard subcommand implementations is possible, but a recipe for code that doesn't work if you're not uber-cautious about following the existing API.

Related