Perl:如何最简单地展开多维数组?

18

如何最简单地将多维数组展平?

7个回答

36

使用map函数进行一级扁平化

$ref = [[1,2,3,4],[5,6,7,8]]; # AoA

@a = map {@$_} @$ref;         # flattens it

print "@a";                   # 1 2 3 4 5 6 7 8

6
值得注意的是,这仅在$ref填充为数组引用时有效。如果混合使用标量和数组引用,则无法正常工作。 - Asmor

19

使用List::Flatten似乎是最简单的方法:

use List::Flatten;

my @foo = (1, 2, [3, 4, 5], 6, [7, 8], 9);        
my @bar = flat @foo;  # @bar contains 9 elements, same as (1 .. 9)

实际上,那个模块只导出了一个简单的函数flat,所以您可以复制源代码

sub flat(@) {
    return map { ref eq 'ARRAY' ? @$_ : $_ } @_;
}

你也可以让它成为递归以支持多级展平:

sub flat {  # no prototype for this one to avoid warnings
    return map { ref eq 'ARRAY' ? flat(@$_) : $_ } @_;
}

9
最简单和最自然的方法是遍历值并使用@运算符来“解引用”/“拆开”任何现有的嵌套值,以获取组成部分。然后对于遇到的每个引用值重复此过程。
这类似于Viajayenders的解决方案,但适用于尚未在数组引用中的值以及任何嵌套级别。
sub flatten {
  map { ref $_ ? flatten(@{$_}) : $_ } @_;
}

试着像这样测试它:

my @l1 = [ 1, [ 2, 3 ], [[[4]]], 5, [6], [[7]], [[8,9]] ];
my @l2 = [ [1,2,3,4,5], [6,7,8,9] ];
my @l3 = (1, 2, [3, 4, 5], 6, [7, 8], 9);  # Example from List::Flatten

my @r1 = flatten(@l1);
my @r2 = flatten(@l1);
my @r3 = flatten(@l3);

if (@r1 ~~ @r2 && @r2 ~~ @r3) { say "All list values equal"; }

与递归不同,但同样的思路:@array=map { ref $_ eq 'ARRAY' ? @$_ : $_ } @array while grep ref $_ eq 'ARRAY', @array 进行迭代。 - hobbs
如果数组包含任何非数组引用,则此答案将抛出异常。你只检查值是否为引用,而不是引用是否为数组引用。提供哈希引用会导致错误“不是ARRAY引用”。 - 3limin4t0r

3
如果数据始终像示例一样,我建议使用List::Flatten。
但是,如果数据有超过2个嵌套数组,flat就无法工作。
例如:@foo = [1, [2, [3, 4, 5]]]
在这种情况下,您应该编写递归代码。
以下是一个示例:
sub flatten {
  my $arg = @_ > 1 ? [@_] : shift;
  my @output = map {ref $_ eq 'ARRAY' ? flatten($_) : $_} @$arg;
  return @output;
}

my @foo = (1, 2, [3, 4, 5, [6, 7, 8]], 9);
my $foo = [1, 2, [3, 4, 5, [6, 7, 8]], 9];
my @output = flatten @foo;
my @output2 = flatten $foo;
print "@output";
print "@output2";

修改后缩短并接受数组引用作为第一个参数。 - Yasuhiro Nakayama
请注意,当提供一个只包含单个标量值的列表时,flatten将返回一个空输出;当提供一个只包含单个非数组引用的列表时,它会抛出一个错误。例如,@input = (42)将导致一个空输出(@{42}导致一个空列表),而@input = ({ name => 'John Doe'})将导致一个错误(@{{name => 'John Doe'}}导致"Not an ARRAY reference")。 - 3limin4t0r

2

如果一个多维数组包含以下内容,最简单的方法是将其展平: 1. 数组 2. 数组引用 3. 标量值 4. 标量引用

sub flatten {
   map { ref $_ eq 'ARRAY' ? flatten(@{$_}) :
         ref $_ eq 'SCALAR' ? flatten(${$_}) : $_
   } @_;
}

另一个展开(sub)答案在标量引用上崩溃。

0

大致意思是:

my $i = 0;

while ($i < scalar(@array)) {
    if (ref @array[$i] eq 'ARRAY') {
        splice @array, $i, 1, @$array[$i];
    } else {
        $i++;
    }
}

我盲目地写了它,不知道它是否真的有效,但你应该能够理解。


0

与Vijayender的解决方案相同,但适用于包含数组引用和标量的混合数组。

$ref = [[1,2,3,4],[5,6,7,8],9,10];
@a = map { ref $_ eq "ARRAY" ? @$_ : $_ } @$ref;
print "@a"

当然,您可以将其扩展为取消引用哈希引用:
@a = map { ref $_ eq "ARRAY" ? @$_ : ref $_ eq "HASH" ? %$_: $_ } $@ref;

或者使用grep来筛选垃圾:

@a = map { @$_} grep { ref $_ eq 'ARRAY' } @$ref;

从List::MoreUtils 0.426开始,我们有一个arrayify函数,可以递归地展开数组:
@a = (1, [[2], 3], 4, [5], 6, [7], 8, 9);
@l = arrayify @a; # returns 1, 2, 3, 4, 5, 6, 7, 8, 9

之前已经介绍过,但是现在出了问题。


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