Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

How to flatten an x-dimensional array?

by Incognito (Pilgrim)
on Mar 12, 2002 at 03:07 UTC ( [id://151036]=perlquestion: print w/replies, xml ) Need Help??

Incognito has asked for the wisdom of the Perl Monks concerning the following question:

I couldn't find this problem on the Q&A section, so I'm wondering how to flatten a multi (x)-dimensional array into one array? We do not know the structure of the array up front, but ultimately want it to end up as one array containing only elements that contain data. Rather than having this array:

$VAR1 = [ '', '' ]; $VAR1 = [ '', '' ]; $VAR1 = [ 'funct1', '', '' ]; $VAR1 = [ '', '' ]; $VAR1 = [ '', [ '', [ 'funct2a', 'funct2b', '' ], '' ], '' ]; $VAR1 = 'funct3'; $VAR1 = 'funct4'; $VAR1 = 'funct5'; $VAR1 = 'funct6'; $VAR1 = 'funct7';
I would like to make the above array (printed using Data::Dumper) into the following:
$VAR1 = 'funct1'; $VAR1 = 'funct2a'; $VAR1 = 'funct2b'; $VAR1 = 'funct3'; $VAR1 = 'funct4'; $VAR1 = 'funct5'; $VAR1 = 'funct6'; $VAR1 = 'funct7';

Is this possible? Of course, I just don't know how...

Replies are listed 'Best First'.
Re: How to flatten an x-dimensional array?
by abstracts (Hermit) on Mar 12, 2002 at 03:34 UTC
    And now for something completely lispish :-)

    my @ar = grep length, flatten(@array); print Dumper(@ar); sub flatten{ return unless @_; my ($car,@cdr) = @_; if(ref($car) eq 'ARRAY'){ flatten(@$car, @cdr); } else { $car, flatten(@cdr); } }
      To Perl-ize this wonderful Lisp solution *grins*
      sub flatten { return unless @_; my $val = shift; return flatten(@$val, @_) if UNIVERSAL::isa($val, 'ARRAY'); return $val, flatten(@_); }

      ------
      We are the carpenters and bricklayers of the Information Age.

      Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

      ++abstracts -- ($car, @cdr) indeed! :-)

      The Sidhekin
      print "Just another Perl ${\(trickster and hacker)},"

•Re: How to flatten an x-dimensional array?
by merlyn (Sage) on Mar 12, 2002 at 14:44 UTC
    Hmm. So far, far too many recursive solutions, when a simple iterative solution works fine:
    sub flatten { my @result; while (@_) { my $first = shift; if (UNIVERSAL::isa($first, "ARRAY")) { # fixed, thanks particle unshift @_, @$first; } else { push @result, $first; } } @result; }

    -- Randal L. Schwartz, Perl hacker

      that is, it works fine if you call UNIVERSAL::isa properly ;)

      it should be:

      if (UNIVERSAL::isa($first, "ARRAY")) {

      ~Particle ;̃

      Note that your iterative solution can be slow with a long output list on any current Perl because iteratively building an array with unshift scales quadratically. (Fixed in the current development cycle.)

      Given the pattern of unshift's and shift's in your code, you might or might not hit this problem. But still for a single reverse you can use the guaranteed efficient pop/push pair instead of shift/unshift:

      sub flatten { my @flattened; # Will be in reverse order while (@_) { my $last = pop; if (UNIVERSAL::isa($last, "ARRAY")) { push @_, @$last; } else { push @flattened, $last; } } return reverse @flattened; }
        I think you missed a reverse there:
        push @_, @$last;
        needs to be
        push @_, reverse @$last;
        since you are reversing the result. Trivial example: $last = [1,2,3,4,5] must flatten as 5, 4, 3, 2, 1 so it can be reversed in the final result to 1, 2, 3, 4, 5 again.

        -- Randal L. Schwartz, Perl hacker

Re: How to flatten an x-dimensional array?
by AidanLee (Chaplain) on Mar 12, 2002 at 03:24 UTC
    consider a recursive solution:
    my @flattened_array = flatten($nested_array_ref); sub flatten { my $array = shift; my @results = (); foreach my $element ( @$array ) { if( ref $element eq 'ARRAY' ) { push @results, flatten($element); } elsif( $element ) { push @results, $element; } } return @results; }

    update: I investigated per rob_au's concerns with the following code using the above subroutine:

    use Data::Dumper; use strict; my $nested_array_ref = [ '', [ 'a', [ 'b', 'c' ], '', 'd' ], 'value' ]; my @flattened_array = flatten($nested_array_ref); print Dumper(\@flattened_array);

    and my output was this:

    $VAR1 = [ 'a', 'b', 'c', 'd', 'value' ];

    the example i gave explicitly passed an array_ref into the function to begin with, and @results is local to the subroutine, and does not get blown away during recursion.

      This doesn't appear to work, primarily because of the clearing of the @results array with each iteration through the flatten function - In addition to this, when called with a unreferenced array, an error is generated when run under strict because of the dereferencing of a string in the foreach loop (@$array).

      Update - Hrmmm, I'll dig into this a bit further and see why my tests failed - Thanks for the update AidanLee :-)

       

      perl -e 's&&rob@cowsnet.com.au&&&split/[@.]/&&s&.com.&_&&&print'

(jeffa) Re: How to flatten an x-dimensional array?
by jeffa (Bishop) on Mar 12, 2002 at 04:00 UTC
    TIMTOWTDI :)
    use strict; use Data::Dumper; my $ref = [ ['',''], ['',''], ['funct1','',''], ['',''], ['',['',['funct2a','funct2b',''],''],''], 'funct3', 'funct4', 'funct5', 'funct6', 'funct7', ]; my @flat = Dumper($ref) =~ /'(\w+)/g; print join("\n",@flat),"\n";

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    B--B--B--B--B--B--B--B--
    H---H---H---H---H---H---
    (the triplet paradiddle with high-hat)
    
Re: How to flatten an x-dimensional array?
by Anonymous Monk on Mar 12, 2002 at 05:19 UTC
    map and grep are your friends. sub flatten { map ref eq 'ARRAY' ? flatten(@$_) : $_, grep defined && length, @_ } Cheers,
    -Anomo
Re: How to flatten an x-dimensional array?
by rob_au (Abbot) on Mar 12, 2002 at 03:24 UTC
    Yes, this is certainly possible - The following simple example uses recursion to iterate through the data object.

    my @array = ( [ '', '' ], [ '', '' ], [ 'funct1', '', '' ], [ '', '' ], [ '', [ '', [ 'funct2a', 'funct2b', '' ], '' ], '' ], 'funct3', 'funct4', 'funct5', 'funct6', 'funct7', ); print join "\n", _flatten( @array ), "\n"; { my @results; sub _flatten { foreach (@_) { if (ref $_ eq 'ARRAY') { _flatten( @{ $_ } ); next; } push @results, $_ if $_; } return @results; } }

    If however, this code is to be used in a production environment, I would consider either rewriting the above to include depth checking and/or a shift towards a iterative rather than recursive loop, or alternatively, reevaluate the code generating this complex data structure in the first place.

     

    perl -e 's&&rob@cowsnet.com.au&&&split/[@.]/&&s&.com.&_&&&print'

      Well, heres a version that responds to a couple of your concerns, and a couple of mine. For instance all of the posted solutions break if the array is blessed, I thought monks at our level weren't supposed to fall into the 'ref' trap ;-). Also I use a variant of 'Annony's solution, but without the grep as I wanted a general solution (note that his use of map AND grep is superfluous, the whole shebang can be done in one map).

      I use overload::StrVal to keep a record of seen nodes to avoid cycles. This means its not ncessary to keep a depth count. Also I cant see much benefit in writing this iteratively. You'd have to maintain your own stack and the overhead for this situation (imo) isn't worth the bother.

      use overload; use Carp; sub flatten { my $ref = shift; my $seen = shift || ""; return map { (UNIVERSAL::isa($_,"ARRAY")) ? do { my $ref_str=overload::StrVal($_); Carp::croak "Cant flatten cyclic data structure !" if index($seen,$ref_str)>=0; flatten($_,$seen."$ref_str"); } : $_ #change to #} : $_ ? $_ :()# to simulate grep } @$ref; } require Data::Dumper; print Data::Dumper::Dumper([ flatten([qw(array of elements), [qw(and arrays)], bless [qw(even blessed ones)],"FooArray" ]) ]); __END__ $VAR1 = [ 'array', 'of', 'elements', 'and', 'arrays', 'even', 'blessed', 'ones' ];

      Fixed spelling mistake.

      Yves / DeMerphq
      --
      When to use Prototypes?
      Advanced Sorting - GRT - Guttman Rosler Transform

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://151036]
Approved by root
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others surveying the Monastery: (4)
As of 2024-04-25 18:12 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found