问题
If I have a procedure or a command in TCL, with variable number of arguments, one can use, if a list's elements are as an input, the "splatter" operator, for example:
set a [list "ko" ]
set m [ list "ok" "bang" ]
lappend a {*}$m
But, what if I want to "twice splatter"? I.e., flatten 2 levels? Using it twice, in sequence, does not work:
set a [list "ko" ]
set m [ list [ list "ok" ] [ list "bang" ] ]
lappend a {*}{*}$m
Will error out on extra character.
回答1:
You've noticed that {*}
(deliberately) doesn't go two steps deep.
Your particular example isn't very indicative of the problem, so I suggest this:
set a [list "ko" ]
set m [list [list "a b" "c d"] [list "e f" "g h"]]
lappend a {*}$m
Here, we get a
set to ko {{a b} {c d}} {{e f} {g h}}
. Which isn't what you wanted. But we can instead do this:
lappend a {*}[concat {*}$m]
Which gives this: ko {a b} {c d} {e f} {g h}
. That looks right.
But are we really doing the right thing here? Let's poke inside with our super-secret introspector, the representation
command:
% tcl::unsupported::representation $m
value is a list with a refcount of 4, object pointer at 0x10085ec50, internal representation 0x103016790:0x0, string representation "{{a b} {c d}}..."
% tcl::unsupported::representation [concat {*}$m]
value is a string with a refcount of 1, object pointer at 0x10085de10, internal representation 0x1030052d0:0x10085f190, string representation "{a b} {c d} {..."
Uh oh! We've lost the list-ness. It's not a catastrophe, but it isn't what we wanted. We instead should really do:
foreach sublist $m {
lappend a {*}$sublist
}
Well, that's more code but preserves list-ness (which would be good if you happened to have precious types on the leaves; core Tcl doesn't have such precious types, but some extensions do).
We can compare the timings:
% time {
set a [list "ko" ]
set m [list [list "a b" "c d"] [list "e f" "g h"]]
lappend a {*}[concat {*}$m]
} 10000
2.852789 microseconds per iteration
% time {
set a [list "ko" ]
set m [list [list "a b" "c d"] [list "e f" "g h"]]
foreach sublist $m {
lappend a {*}$sublist
}
} 10000
4.022959 microseconds per iteration
Oh…
% time {apply {{} {
set a [list "ko" ]
set m [list [list "a b" "c d"] [list "e f" "g h"]]
lappend a {*}[concat {*}$m]
}}} 10000
2.4486125 microseconds per iteration
% time {apply {{} {
set a [list "ko" ]
set m [list [list "a b" "c d"] [list "e f" "g h"]]
foreach sublist $m {
lappend a {*}$sublist
}
}}} 10000
1.6870501 microseconds per iteration
Hah! The type-correct one is better in a procedure(-like context).
回答2:
The {*}
syntax doesn’t really flatten a list per se. Flattening is an execution-level activity, while argument expansion is a parsing-level activity.
% set a a
% lappend a {*}{{a b} {c d}}
In this example, the items in the list {{a b} {c d}}
are spliced into the command line as two separate arguments:
% lappend a {a b} {c d}
a {a b} {c d}
If you need to flatten one more level, you should do so with a command “between” the expansions:
% lappend a {*}[concat {*}{{a b} {c d}}]
a a b c d
Documentation: concat, lappend, {*} (syntax), Summary of Tcl language syntax
来源:https://stackoverflow.com/questions/43680723/two-level-splatter-tcl