sid_com
sid_com

Reputation: 25117

Perl: What is the easiest way to flatten a multidimensional array?

What's the easiest way to flatten a multidimensional array ?

Upvotes: 18

Views: 13010

Answers (7)

Eugene Yarmash
Eugene Yarmash

Reputation: 149933

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(@$_) : $_ } @_;
}

Upvotes: 19

petre
petre

Reputation: 99

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.

Upvotes: 0

Yasuhiro Nakayama
Yasuhiro Nakayama

Reputation: 49

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";

Upvotes: 3

djacopille
djacopille

Reputation: 21

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.

Upvotes: 2

Garen
Garen

Reputation: 961

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"; }

Upvotes: 9

Vijayender
Vijayender

Reputation: 1565

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

Upvotes: 36

Mrki
Mrki

Reputation: 367

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.

Upvotes: 0

Related Questions