Perl: What is the easiest way to flatten a multidimensional array?
What's the easiest way t开发者_开发问答o flatten a multidimensional array ?
One level of flattening using 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
Using List::Flatten
seems like the easiest:
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)
Actually, that module exports a single simple function flat
, so you might as well copy the source code:
sub flat(@) {
return map { ref eq 'ARRAY' ? @$_ : $_ } @_;
}
You could also make it recursive to support more than one level of flattening:
sub flat { # no prototype for this one to avoid warnings
return map { ref eq 'ARRAY' ? flat(@$_) : $_ } @_;
}
The easiest and most natural way, is to iterate over the values and use the @ operator to "dereference" / "unpack" any existing nested values to get the constituent parts. Then repeat the process for every reference value encountered.
This is similar to Viajayenders solution, but works for values not already in an array reference and for any level of nesting:
sub flatten {
map { ref $_ ? flatten(@{$_}) : $_ } @_;
}
Try testing it like so:
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"; }
if data is always like an example, I recommend List::Flatten too.
but data has more than 2 nested array, flat cant't work.
like @foo = [1, [2, [3, 4, 5]]]
in that case, you should write recursive code for it.
how about bellow.
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";
The easiest way to flatten a multidimensional array when it includes: 1. arrays 2. array references 3. scalar values 4. scalar references
sub flatten {
map { ref $_ eq 'ARRAY' ? flatten(@{$_}) :
ref $_ eq 'SCALAR' ? flatten(${$_}) : $_
} @_;
}
The other flatten sub answer crashes on scalar references.
Something along the lines of:
my $i = 0;
while ($i < scalar(@array)) {
if (ref @array[$i] eq 'ARRAY') {
splice @array, $i, 1, @$array[$i];
} else {
$i++;
}
}
I wrote it blindly, no idea if it actually works but you should get the idea.
Same as Vijayender's solution but will work on mixed arrays containing arrayrefs and scalars.
$ref = [[1,2,3,4],[5,6,7,8],9,10];
@a = map { ref $_ eq "ARRAY" ? @$_ : $_ } @$ref;
print "@a"
Of course you can extend it to also dereference hashrefs:
@a = map { ref $_ eq "ARRAY" ? @$_ : ref $_ eq "HASH" ? %$_: $_ } $@ref;
or use grep to weed out garbage:
@a = map { @$_} grep { ref $_ eq 'ARRAY' } @$ref;
As of List::MoreUtils 0.426 we have an arrayify function that flattens arrays recursively:
@a = (1, [[2], 3], 4, [5], 6, [7], 8, 9);
@l = arrayify @a; # returns 1, 2, 3, 4, 5, 6, 7, 8, 9
It was introduced earlier but was broken.
精彩评论