ajwood
ajwood

Reputation: 19047

Expanding list refs in Perl

I have a list in Perl, and I'm trying to "expand" any lists it contains.

For example:

my @l = (
  'foo',
  ['A', 'B'],
  'bar',
  ['X', 'Y', 'Z'],
  'baz',
);

I'd like to expand the lists to get this:

my @want = (
  [qw/ foo A bar X baz /],
  [qw/ foo A bar Y baz /],
  [qw/ foo A bar Z baz /],
  [qw/ foo B bar X baz /],
  [qw/ foo B bar Y baz /],
  [qw/ foo B bar Z baz /],
);

I came up with this, which works, but is ugly and doesn't result in sensibly ordered array:

my @out = ([]);
foreach my $elt (@l){
    if( ref($elt) eq 'ARRAY' ){
        my $n = scalar(@$elt);
        my @_out;
        # expand 
        for( my $i=0; $i<$n; $i++ ){
            foreach my $o (@out){
                push(@_out, [@$o]);
            }
        }
        for( my $i=0; $i<@_out; $i++ ){
            push(@{$_out[$i]}, $elt->[$i % $n]);
        }
        @out = @_out;
    }
    else{
        foreach my $o (@out){
            push(@$o, $elt);
        }
    }
}

Is there a more concise way to accomplish this operation?

Upvotes: 0

Views: 94

Answers (2)

Chris Charley
Chris Charley

Reputation: 6613

Although this is an old thread, I thought I'd add another possible solution

The module Set::CrossProduct will also get the result wanted. (The input array elements must all be array references.)

#!/usr/bin/perl
use strict;
use warnings;
use Data::Dump;
use Set::CrossProduct;

my @l = (
  'foo',
  ['A', 'B'],
  'bar',
  ['X', 'Y', 'Z'],
  'baz',
);

# all elements of array must be array refs
@l = map ref eq 'ARRAY' ? $_ : [$_], @l;

my @wanted = Set::CrossProduct->new(\@l)->combinations;

dd \@wanted;

The data dump output was

[
  ["foo", "A", "bar", "X", "baz"],
  ["foo", "A", "bar", "Y", "baz"],
  ["foo", "A", "bar", "Z", "baz"],
  ["foo", "B", "bar", "X", "baz"],
  ["foo", "B", "bar", "Y", "baz"],
  ["foo", "B", "bar", "Z", "baz"],
]

Upvotes: 0

Hunter McMillen
Hunter McMillen

Reputation: 61540

It looks like you are trying to compute the Cartesian product of the elements in your array. You can use Math::Cartesian::Product from CPAN to do this:

use strict; 
use warnings; 

use Math::Cartesian::Product;
cartesian { print "@_","\n"; } 
   ([qw(foo)], [qw(A B)], [qw(bar)], [qw(X Y Z)], [qw(baz)]);

# outputs
# foo A bar X baz
# foo A bar Y baz
# foo A bar Z baz
# foo B bar X baz
# foo B bar Y baz
# foo B bar Z baz

Note, this does require you to place each of your input elements in an arrayref and if you want an array of arrayrefs as your output (from your example) you will need to encode that logic in the block.

Upvotes: 2

Related Questions