ajwood ajwood - 5 months ago 19
Perl Question

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?

Answer

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.