代码之家  ›  专栏  ›  技术社区  ›  erjiang

在Perl中,如何迭代多个集合的笛卡尔积?

  •  4
  • erjiang  · 技术社区  · 15 年前

    鉴于 x 数组的数目,每个数组中可能有不同数目的元素,我如何迭代从每个数组中选择一个项的所有组合?

    例子:

    [   ]   [   ]   [   ]
     foo     cat      1
     bar     dog      2
     baz              3
                      4
    

    退换商品

    [foo]   [cat]   [ 1 ]
    [foo]   [cat]   [ 2 ]
      ...
    [baz]   [dog]   [ 4 ]
    

    我用Perl做这个,顺便说一句。

    5 回复  |  直到 6 年前
        1
  •  21
  •   brian d foy JRFerguson    6 年前

    我的 Set::CrossProduct 模块完全满足您的需要。请注意,您并没有真正寻找排列,这是一个集合中元素的顺序。您要寻找的是交叉积,它是来自不同集合的元素的组合。

    我的模块为您提供了一个迭代器,所以您不需要在内存中创建它。只有在需要时才创建新的元组。

    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
  •  2
  •   bdonlan    15 年前

    针对任意数量列表的简单递归解决方案:

    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;
    }
    

    对于任意数量的列表而言,这是一个不那么简单的非递归解决方案:

    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
  •  1
  •   erjiang    15 年前

    有关笛卡尔积的递归和更流畅的Perl示例(带有注释和文档),请参见 http://www.perlmonks.org/?node_id=7366

    例子:

    sub cartesian {
        my @C = map { [ $_ ] } @{ shift @_ };
    
        foreach (@_) {
            my @A = @$_;
    
            @C = map { my $n = $_; map { [ $n, @$_ ] } @C } @A;
        }
    
        return @C;
    }
    
        4
  •  0
  •   erjiang    15 年前

    我首先想到的一个方法是使用一对循环,而不是递归。

    1. 查找排列总数
    2. 从0到总排列-1的循环
    3. 注意,通过将循环索引模数取为数组中元素的数目,可以得到每个排列。

    例子:

    给定a[3]、b[2]、c[3],

    for (index = 0..totalpermutations) {
        print A[index % 3];
        print B[(index / 3) % 2];
        print C[(index / 6) % 3];
    }
    

    当然,其中一个for循环可以被替换成loop over[a b c…],一小部分可以被memoize。当然,递归更整洁,但这对于递归严重受堆栈大小限制的语言可能有用。

        5
  •  0
  •   ikegami    6 年前

    可以使用嵌套循环。

    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); 
       ...
    }}}
    

    当需要任意数量的嵌套循环时,可以使用 Algorithm::Loops 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->() ) {
       ...
    }