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

What is a good algorithm to generate all the possible ordered combinations of the elements of an arbitrary number of arrays?

For example, given the following arrays:

my @a = qw/A B C D E F/; my @b = qw/1 2 3 4 5 6/; my @c = qw/apple orange pomegranate grape/; my @d = qw/wolf lion dog cat cow ape whale/;

how can I generate a list like this:

[A, 1, apple, wolf] [A, 1, apple, lion] [A, 1, apple, dog] ... [C, 5, pomegranate, ape] [C, 5, pomegranate, whale] [C, 5, grape, wolf] [C, 5, grape, lion] ... [F, 6, grape, whale]

if I don't know how many such arrays I will have to start with?

Replies are listed 'Best First'.
Re: generating combinations of elements of an arbitrary number of arrays
by brian_d_foy (Abbot) on Mar 15, 2005 at 21:16 UTC
Re: generating combinations of elements of an arbitrary number of arrays
by dragonchild (Archbishop) on Mar 15, 2005 at 20:49 UTC
    Algorithm::Loops is a good place to start.

    Being right, does not endow the right to be rude; politeness costs nothing.
    Being unknowing, is not the same as being stupid.
    Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
    Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

      Hmm, that looks useful -- I'll check it out. Thanks!
Re: generating combinations of elements of an arbitrary number of arrays
by ikegami (Patriarch) on Mar 15, 2005 at 22:42 UTC

    A fun alternative that uses glob:

    use strict; use warnings; my @lists = ( [ qw/A B C D E F/ ], [ qw/1 2 3 4 5 6/ ], [ qw/apple orange pomegranate grape/ ], [ qw/wolf lion dog cat cow ape whale/ ], ); my @ranges = map { [ 0..$#$_ ] } @lists; my $glob_string = join '\\ ', map { '{'.join(',', @$_).'}' } @ranges; my @results; while (glob($glob_string)) { my $i = 0; push(@results, [ map { $lists[$i++][$_] } split ]); } print(join(' ', @$_), $/) foreach @results;

      Same idea, but with a generator:

      use strict; use warnings; sub make_generator { my ($lists) = @_; my @ranges = map { [ 0..$#$_ ] } @$lists; my $glob_string = join '\\ ', map { '{'.join(',', @$_).'}' } @ranges; return sub { local $_ = scalar glob $glob_string; return unless defined $_; my $i = 0; return [ map { $lists[$i++][$_] } split ]; } }; { my @lists = ( [ qw/A B C D E F/ ], [ qw/1 2 3 4 5 6/ ], [ qw/apple orange pomegranate grape/ ], [ qw/wolf lion dog cat cow ape whale/ ], ); my $generator = make_generator(\@lists); print(join(' ', @$_), $/) while ($_ = &$generator()); }
Re: generating combinations of elements of an arbitrary number of arrays
by shotgunefx (Parson) on Mar 15, 2005 at 21:59 UTC
    Also a snippet here


    -Lee

    "To be civilized is to deny one's nature."
Re: generating combinations of elements of an arbitrary number of arrays
by mickey (Acolyte) on Mar 16, 2005 at 16:49 UTC

    Thanks to everyone for all the responses. Set::CrossProduct does indeed do exactly what I wanted, except that the real problem I'm working with wasn't using straight arrays, and it seemed complicated to get Set::CrossProduct to do what I wanted with the inputs I have.

    So, in case you're interested, here's the solution I came up with in the end. I've just included one element of the DATA hash at the end; there are lots like that. Any comments or suggestions you have would be welcomed.

    local $\ = "\n"; local $/ = ''; open CODES, "> $code_file" or die "Couldn't open codefile: $!\n"; my $csv = Text::CSV->new; $csv->combine('code', 'desc'); print CODES $csv->string; my %hash = eval <DATA>; my @full_code_list = (); foreach my $key (keys %hash) { print "Looking at code [$key]...\n"; my @list = gen_codes($hash{$key}); push @full_code_list, @list if @list; } foreach my $aref (@full_code_list) { my ($code, $desc) = @$aref; $csv->combine($code, $desc); print CODES $csv->string; } sub gen_codes { my $href = shift; my $source = $href->{'source'}; my $base_desc = $href->{'desc'}; my @codes = (); my @element_hashes = @$href{@{$href->{'elements'}}}; my $code_str_sub = $href->{'code_str'}; my $desc_str_sub = $href->{'desc_str'}; my @code_args = ($source); my @desc_args = ($base_desc); sub getvalues { my ($elems, $codes, $c_sub, $d_sub, $code_args, $desc_args) = +@_; my ($element, @remaining_elems) = @$elems; for my $code (keys %$element) { my $desc = $element->{$code}; my $c_args = [ @$code_args, $code ]; my $d_args = [ @$desc_args, $desc ]; if (@remaining_elems) { getvalues(\@remaining_elems, $codes, $c_sub, $d_sub, $ +c_args, $d_args); } else { push @$codes, [&$c_sub(@$c_args), &$d_sub(@$d_args)]; } } } getvalues(\@element_hashes, \@codes, $code_str_sub, $desc_str_sub, + \@code_args, \@desc_args); return @codes; } __END__ my %hash = ( 'FI_SWP_PLAIN_VANILLA-SPREADS' => { source => 'FDER,SWAPSPRD', desc => 'Swaps - Plain Vanilla Spreads', elements => [qw/term attr/], term => { '2Y' => '2 Years', '3Y' => '3 Years', '4Y' => '4 Years', '5Y' => '5 Years', '6Y' => '6 Years', '7Y' => '7 Years', '8Y' => '8 Years', '9Y' => '9 Years', '10Y' => '10 Years', '12Y' => '12 Years', '15Y' => '15 Years', '20Y' => '20 Years', '25Y' => '25 Years', '30Y' => '30 Years', '40Y' => '40 Years' }, attr => { RT_MID => 'Mid Rate', RT_BID => 'Bid Rate', RT_OFF => 'Offer Rate', AM_MOD_DUR => 'Modified Duration', DT_VALUE => 'Value Date', DT_MATURITY => 'Maturity Date' }, code_str => sub { my ($s,$t,$a) = @_; return "DB($s,$t,$a)"; }, desc_str => sub { my ($d,$t,$a) = @_; return "$d: $t $a"; } } );
Re: generating combinations of elements of an arbitrary number of arrays
by ambrus (Abbot) on Mar 15, 2005 at 23:20 UTC
Re: generating combinations of elements of an arbitrary number of arrays
by RazorbladeBidet (Friar) on Mar 15, 2005 at 20:54 UTC
    You can push the array refs onto a master array and loop through the master array and operate on the arrayrefs.

    I had a similar question. The individual arrays contained arrayrefs, but the answer should be close, or hopefully at the least a good starting point.
    --------------
    It's sad that a family can be torn apart by such a such a simple thing as a pack of wild dogs