2009-08-10 97 views
4

鉴于x数组的数量,每个数组的数量可能不同,我如何遍历所有组合,我从每个数组中选择一个项目?在Perl中,如何迭代多个集合的笛卡尔乘积?

例子:

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

返回

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

我这样做在Perl,顺便说一句。

+1

有不少关于计算器已经这个话题;只搜索“排列”。我没有特别检查是否有perl。 – balpha 2009-08-10 17:09:18

+2

“排列”是一个错误的搜索,因为这不是一个排列组合。 – 2010-07-05 07:23:00

回答

0

递归和更流畅的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; 
} 
2

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

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"; 
} 
+0

+1非常优雅,感谢分享 – dfa 2009-08-10 17:17:56

+0

请注意,这里有一些不必要的分配 - 它可以进一步优化。但这种一般方法是你想要的:) – bdonlan 2009-08-10 17:25:21

+3

这不是你想要的方法。避免在Perl中递归,并且不要在内存中创建整个事物。 – 2009-08-10 18:11:27

0

还有一个方法,我首先想到使用一对夫妇的循环和没有递归。

  1. 找到排列的总数
  2. 从0
  3. 环路total_permutations-1
  4. 观察到,通过利用循环索引模数元素的数目在一个阵列,可以得到每一个排列

实施例:

鉴于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循环代替循环[A B C ...],并且可以记忆一小部分。当然,递归更简洁,但这对于递归受堆栈大小严重限制的语言可能很有用。

+1

我认为它和三个嵌套循环一样,除了这个方法你也花时间在这个过程中做数学。 – 2009-08-10 18:30:25

+0

循环三元组效率高且易于阅读 – dfa 2009-08-10 18:32:46

+0

只需少量工作,此方法可用于任何数量的列表,这对嵌套for循环无法说明。 – barrycarter 2014-05-10 01:43:10

18

我的Set::CrossProduct模块正是你想要的。请注意,你并不是真的在寻找排列,这是排列中的元素排序。您正在寻找交叉产品,这是来自不同组合的元素的组合。

我的模块给你一个迭代器,所以你不会在内存中创建它。只有在需要时才创建一个新的元组。

+0

+1 ...我没有看到你的帖子,当我尝试重复这个模块的功能时(我也不知道它是否存在)。 – 2009-08-10 18:52:48