In Perl, how can I iterate over the Cartesian product of multiple sets?

前端 未结 5 773
Happy的楠姐
Happy的楠姐 2020-12-21 02:21

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?

相关标签:
5条回答
  • 2020-12-21 02:32

    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;
    }
    
    0 讨论(0)
  • 2020-12-21 02:37

    There's one method I thought of first that uses a couple for loops and no recursion.

    1. find total number of permutations
    2. loop from 0 to total_permutations-1
    3. 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.

    0 讨论(0)
  • 2020-12-21 02:42

    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";
    }
    
    0 讨论(0)
  • 2020-12-21 02:44

    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->() ) {
       ...
    }
    
    0 讨论(0)
  • 2020-12-21 02:48

    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->@*;
        }
    
    0 讨论(0)
提交回复
热议问题