我有 N 个列表,例如2 个这样的列表:
{a {1 2 3}}
和 {b {4 5 6}}
。
我想重新映射值以获得此列表:
{{{a 1} {b 4}} {{a 2} {b 5}} {{a 3} {b 6}}}
从 2 个或更多列表开始执行上述操作,并且每个列表的第二部分的数量可以是可变的,即
{{a {1 2 3 4 5 .. n}}
,那么什么是一个好的算法?
我正在使用 Tcl 8.5(我无法升级)。
我不知道你为什么想要这种转换,但据我了解你想要什么,这会做到这一点:
proc weirdshuffle lists {
set tags {}
set iterators {}
foreach l $lists {
lassign $l tag vals
lappend tags $tag
lappend iterators $tag $vals
}
lmap {*}$iterators {
lmap tag $tags {
list $tag [set $tag]
}
}
}
proc weirdshuffle8.5 lists {
set tags {}
set iterators {}
foreach l $lists {
lassign $l tag vals
lappend tags $tag
lappend iterators $tag $vals
}
set res {}
foreach {*}$iterators {
set tmp {}
foreach tag $tags {
lappend tmp [list $tag [set $tag]]
}
lappend res $tmp
}
set res
}
puts [weirdshuffle8.5 {
{a {1 2 3}}
{b {4 5 6}}
{c {7 8 9}}
}]
使用
lmap
版本更容易理解它的功能,但该命令首先出现在 Tcl 8.6 中,因此 weirdshuffle8.5
是一个显式处理循环累加器的实现。
核心思想是利用
foreach
(和lmap
)支持不同值列表上的多个迭代器这一事实,并构造迭代器和列表的列表,然后在这些上运行lmap
(或foreach
) .
对于偏执者来说,重要的是要认识到这样做会根据传入列表的值在
weirdshuffle
中创建(或覆盖)变量,如果其中一个与 proc 使用的变量冲突,可能会破坏事情。以一些视觉混乱为代价,可以通过在数组中设置索引来避免这种情况:
proc weirdshuffle_safe lists {
set tags {}
set iterators {}
foreach l $lists {
lassign $l tag vals
lappend tags t($tag) $tag
lappend iterators t($tag) $vals
}
lmap {*}$iterators {
lmap {tag name} $tags {
list $name [set $tag]
}
}
}
这也可以防止更病态的情况,即迭代器变量看起来像其他名称空间中的完全限定变量:
{::foo::bar {1 2 2}} {::global {4 5 6}}
否则可以在过程调用框架之外设置任意状态。如果您无法控制这些标签值(示例中的 a
和 b
),那么我会使用 _safe
变体来确保。
这可以处理任意数量的列表,以及不同长度的子列表:
# incoming data
set lists {{a {1 2 3}} {b {4 5 6}} {c {11 22}} {d {100 200 300 400}}}
# split into "labels" and "data"
foreach lst $lists {
lappend labels [lindex $lst 0]
lappend data [lindex $lst 1]
}
# find the length of the longest sublist
set maxlen [::tcl::mathfunc::max {*}[lmap d $data {llength $d}]]
# create the resulting matrix
set result [lrepeat [llength $labels] {}]
for {set i 0} {$i < $maxlen} {incr i} {
for {set j 0} {$j < [llength $labels]} {incr j} {
lset result $i end+1 [list [lindex $labels $j] [lindex $data $j $i]]
}
}
检查结果:
puts $result
# => {{a 1} {b 4} {c 11} {d 100}} {{a 2} {b 5} {c 22} {d 200}} {{a 3} {b 6} {c {}} {d 300}} {{a {}} {b {}} {c {}} {d 400}}
# or more readably:
foreach res $result {puts [list $res]}
# => {{a 1} {b 4} {c 11} {d 100}}
# => {{a 2} {b 5} {c 22} {d 200}}
# => {{a 3} {b 6} {c {}} {d 300}}
# => {{a {}} {b {}} {c {}} {d 400}}
不幸的是,我无法从 8.5 升级到 8.6,因此无法使用 lmap。
幸运的是,
lmap
在 8.5 中实现起来很简单——这实现了 lmap 最简单的用法,一次迭代一个列表一个元素。
proc lmap {varname lst body} {
upvar $varname var
set result [list]
foreach var $lst {
lappend result [uplevel $body]
}
return $result
}