问题
Given x
number of arrays, each with a possibly different number of elements, how can I iterate through all combinations where I select one item from each array?
Example:
[ ] [ ] [ ]
foo cat 1
bar dog 2
baz 3
4
Returns
[foo] [cat] [ 1 ]
[foo] [cat] [ 2 ]
...
[baz] [dog] [ 4 ]
I'm doing this in Perl, btw.
回答1:
My Set::CrossProduct module does exactly what you want. Note that you aren't really looking for permutations, which is the ordering of the elements in a set. You're looking for the cross product, which is the combinations of elements from different sets.
My module gives you an iterator, so you don't create it all in memory. You create a new tuple only when you need it.
use Set::Crossproduct;
my $iterator = Set::CrossProduct->new(
[
[qw( foo bar baz )],
[qw( cat dog )],
[qw( 1 2 3 4 )],
]
);
while( my $tuple = $iterator->get ) {
say join ' ', $tuple->@*;
}
回答2:
A simple recursive solution for an arbitrary number of lists:
sub permute {
my ($first_list, @remain) = @_;
unless (defined($first_list)) {
return []; # only possibility is the null set
}
my @accum;
for my $elem (@$first_list) {
push @accum, (map { [$elem, @$_] } permute(@remain));
}
return @accum;
}
A not-so-simple non-recursive solution for an arbitrary number of lists:
sub make_generator {
my @lists = reverse @_;
my @state = map { 0 } @lists;
return sub {
my $i = 0;
return undef unless defined $state[0];
while ($i < @lists) {
$state[$i]++;
last if $state[$i] < scalar @{$lists[$i]};
$state[$i] = 0;
$i++;
}
if ($i >= @state) {
## Sabotage things so we don't produce any more values
$state[0] = undef;
return undef;
}
my @out;
for (0..$#state) {
push @out, $lists[$_][$state[$_]];
}
return [reverse @out];
};
}
my $gen = make_generator([qw/foo bar baz/], [qw/cat dog/], [1..4]);
while ($_ = $gen->()) {
print join(", ", @$_), "\n";
}
回答3:
Recursive and more-fluent Perl examples (with commentary and documentation) for doing the Cartesian product can be found at http://www.perlmonks.org/?node_id=7366
Example:
sub cartesian {
my @C = map { [ $_ ] } @{ shift @_ };
foreach (@_) {
my @A = @$_;
@C = map { my $n = $_; map { [ $n, @$_ ] } @C } @A;
}
return @C;
}
回答4:
There's one method I thought of first that uses a couple for loops and no recursion.
- find total number of permutations
- loop from 0 to total_permutations-1
- observe that, by taking the loop index modulus the number of elements in an array, you can get every permutations
Example:
Given A[3], B[2], C[3],
for (index = 0..totalpermutations) {
print A[index % 3];
print B[(index / 3) % 2];
print C[(index / 6) % 3];
}
where of course a for loop can be substituted to loop over [A B C ...], and a small part can be memoized. Of course, recursion is neater, but this might be useful for languages in which recursion is severely limited by stack size.
回答5:
You can use nested loops.
for my $e1 (qw( foo bar baz )) {
for my $e2 (qw( cat dog )) {
for my $e3 (qw( 1 2 3 4 )) {
my @choice = ($e1, $e2, $e3);
...
}}}
When you need an arbitrary number of nested loops, you can use Algorithm::Loops's NestedLoops
.
use Algorithm::Loops qw( NestedLoops );
my @lists = (
[qw( foo bar baz )],
[qw( cat dog )],
[qw( 1 2 3 4 )],
);
my $iter = NestedLoops(\@lists);
while ( my @choice = $iter->() ) {
...
}
来源:https://stackoverflow.com/questions/1256036/in-perl-how-can-i-iterate-over-the-cartesian-product-of-multiple-sets