如何完全展开一个列表(包含列表的列表(的列表)...)?

20

我在思考如何完全展平包含列表的东西。除其他事项外,我想出了这个解决方案,可以将具有多个元素的东西压缩并放回去,或者在滑动后获取具有一个元素的东西。

这与如何在perl 6中“展平”列表的列表?有些不同,因为任务是重新结构而不是完全平坦。

但是,也许有更好的方法。

my @a  = 'a', ('b', 'c' );
my @b  = ('d',), 'e', 'f', @a;
my @c  = 'x', $( 'y', 'z' ), 'w';

my @ab = @a, @b, @c;
say "ab: ", @ab;

my @f = @ab;

@f = gather {
    while @f {
        @f[0].elems == 1 ??
            take @f.shift.Slip
                !!
            @f.unshift( @f.shift.Slip )
        }
    }

say "f: ", @f;

这会得到:

ab: [[a (b c)] [(d) e f [a (b c)]] [x (y z) w]]
f: [a b c d e f a b c x y z w]

有趣的是,我也阅读了一些Python的回答:

itertools.chain(*sublist) 看起来很有趣,但回答要么采用递归,要么限制为硬编码的两个层级。函数式语言在源代码中使用递归,但这是我预料到的。

2个回答

13

很遗憾,即使子列表被包含在项目容器中,也没有直接内置的方法完全展平数据结构。

一些可能的解决方案:

收集/获取

您已经提出了这样的解决方案,但是 deepmap 可以处理所有树迭代逻辑以简化它。其回调函数对于数据结构的每个叶节点调用一次,因此使用 take 作为回调意味着 gather 将收集叶值的平坦列表:

sub reallyflat (+@list) { gather @list.deepmap: *.take }

自定义递归函数

您可以使用这样的子例程来递归地将列表“slip”到其父级中:

multi reallyflat (@list) { @list.map: { slip reallyflat $_ } }
multi reallyflat (\leaf) { leaf }

另一种方法是递归地将 <> 应用于子列表,以解除它们所包含的任何项目容器,然后对结果调用 flat

sub reallyflat (+@list) {
    flat do for @list {
        when Iterable { reallyflat $_<> }
        default       { $_               }
    }
}

多维数组索引

postcircumfix [ ]运算符可与多维下标一起使用,以获取到达特定深度的叶节点的平面列表,但不幸的是,“无限深度”版本尚未实现:

say @ab[*;*];     # (a (b c) (d) e f [a (b c)] x (y z) w)
say @ab[*;*;*];   # (a b c d e f a (b c) x y z w)
say @ab[*;*;*;*]; # (a b c d e f a b c x y z w)
say @ab[**];      # HyperWhatever in array index not yet implemented. Sorry.

如果您知道数据结构的最大深度,这是一个可行的解决方案。

避免容器化

内置的flat函数可以很好地展平嵌套列表,但问题在于它不会进入项目容器(Scalar)。嵌套列表中无意创建项目容器的常见情况包括:

  • 一个Array(但不是List)将其每个元素都包装在一个新的项目容器中,无论它之前是否有容器。

    • 如何避免: 如果您不需要数组提供的可变性,则使用列表而不是数组。 使用:=绑定而不是赋值,可以将List存储在@变量中,而不将其转换为Array:
      my @a  := 'a', ('b', 'c' );
      my @b  := ('d',), 'e', 'f', @a;
      say flat @b; # (d e f a b c)
  • $变量是项目容器。

    • 如何避免: 在将列表存储在$变量中,然后将其作为元素插入另一个列表时,使用<>将其取消容器化。使用|将其传递给flat时,也可以通过绕过父列表的容器来避免容器化:
      my $a = (3, 4, 5);
      my $b = (1, 2, $a<>, 6);
      say flat |$b; # (1 2 3 4 5 6)

1
有没有可能在6.d中添加这样的deepflat函数(或运算符,如果还没有被使用,我提名||)? - mscha
2
我有点喜欢使用没有括号的现有内置函数:say gather @ab.deepmap: *.take; - raiph
@mscha:如果实现了@ab[**],可能就不需要另一个运算符了。 |将某些内容插入到外部列表中,它不会影响应用于内部结构的列表-因此我认为||不是理想的选择。此外,我认为其中一个设计文档已经将||标记为将列表转换为多维下标的方法,例如@tree [||@path] - smls
@raiph,smls:有道理。 - mscha
3
Deepmap 赢了。至于“避免容器”这一点,有时候你必须接受别人给你的东西。最好的解决方案应该能够处理所有情况。 - brian d foy
显示剩余2条评论

9

我不知道是否有内置的方法可以这样做,但很可能有(如果没有,那么可能应该有)。

在短时间内,我能想到的最好的方法是:

gather @ab.deepmap(*.take)

我不确定gather/take如何与可能并行评估的超算符交互,因此以下替代方案可能不安全,特别是如果您关心元素顺序:

gather @ab>>.take

如果你需要一个数组,你可以将代码放入方括号中;或者通过.list将其实现为列表。

最后,这是第一个解决方案被改写成了复古风格的子例程:

sub deepflat { gather deepmap &take, @_ }

1
嘿,我没有看到你在我写答案的时候已经想出了相同的“deepmap”解决方案... 是的,一旦“»”像设计文档中预期的那样学会并行化自身,你的第二个解决方案很可能不会按正确顺序返回元素。 - smls
1
[x]去掉迷信的花括号(并添加了一个deepflat实现) - Christoph

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接