(已解决)Tcl - 将 2 个列表重新映射为一个列表

问题描述 投票:0回答:2

我有 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(我无法升级)。

tcl
2个回答
3
投票

我不知道你为什么想要这种转换,但据我了解你想要什么,这会做到这一点:

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
变体来确保。


2
投票

这可以处理任意数量的列表,以及不同长度的子列表:

# 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
}
© www.soinside.com 2019 - 2024. All rights reserved.