在Perl中,我如何获得多组笛卡尔积?

时间:2010-03-16 18:34:58

标签: perl cartesian-product

我想在Perl中进行排列。例如,我有三个数组:["big", "tiny", "small"]然后我有["red", "yellow", "green"]["apple", "pear", "banana"]

我如何获得:

["big", "red", "apple"]
["big", "red", "pear"]

..etc..

["small", "green", "banana"]

我理解这称为排列。但我不知道该怎么做。另外我不知道我可以拥有多少阵列。可能有三个或四个,所以我不想做嵌套循环。

6 个答案:

答案 0 :(得分:14)

这实际上不是排列,而是Cartesian product。请参阅Math::Cartesian::Product

#!/usr/bin/perl

use strict; use warnings;

use Math::Cartesian::Product;

cartesian { print "@_\n" }
    ["big", "tiny", "small"],
    ["red", "yellow", "green"],
    ["apple", "pear", "banana"];

输出:

C:\Temp> uu
big red apple
big red pear
big red banana
big yellow apple
big yellow pear
big yellow banana
big green apple
big green pear
big green banana
tiny red apple
tiny red pear
tiny red banana
tiny yellow apple
tiny yellow pear
tiny yellow banana
tiny green apple
tiny green pear
tiny green banana
small red apple
small red pear
small red banana
small yellow apple
small yellow pear
small yellow banana
small green apple
small green pear
small green banana

答案 1 :(得分:6)

几年前我必须解决这个问题。我无法提出自己的解决方案,而是遇到了这段精彩的代码,其中涉及map以及递归的巧妙和明智的使用:

#!/usr/bin/perl

print "permute:\n";
print "[", join(", ", @$_), "]\n" for permute([1,2,3], [4,5,6], [7,8,9]);

sub permute {

    my $last = pop @_;

    unless(@_) {
           return map([$_], @$last);
    }

    return map { 
                 my $left = $_; 
                 map([@$left, $_], @$last)
               } 
               permute(@_);
}

是的,这看起来很疯狂,但请允许我解释一下!该函数将递归直到@_为空,此时它将([1], [2], [3])(三个arrayrefs的列表)返回到先前的递归级别。在该级别$last是对包含[4, 5, 6]的数组的引用。

然后将外部地图的正文运行三次,$_设置为[1],然后[2],最后[3]。然后,对于外部地图的每次迭代,内部地图将在(4, 5, 6)上运行,这将返回([1, 4], [1, 5], [1, 6])([2, 4], [2, 5], [2, 6]),最后返回([3, 4], [3, 5], [3, 6])

最后一个递归调用然后返回([1, 4], [1, 5], [1, 6], [2, 4], [2, 5], [2, 6], [3, 4], [3, 5], [3, 6])

然后,它会针对[7,8,9]运行该结果,从而为您提供[1, 4, 7], [1, 4, 8], [1, 4, 9], [1, 5, 7], [1, 5, 8], [1, 5, 9], [1, 6, 7], [1, 6, 8], [1, 6, 9], [2, 4, 7], [2, 4, 8], [2, 4, 9], [2, 5, 7], [2, 5, 8], [2, 5, 9], [2, 6, 7], [2, 6, 8], [2, 6, 9], [3, 4, 7], [3, 4, 8], [3, 4, 9], [3, 5, 7], [3, 5, 8], [3, 5, 9], [3, 6, 7], [3, 6, 8], [3, 6, 9]

我记得在perlmonks.org上发帖询问有人向我解释这个问题。

您可以轻松地将此解决方案适应您的问题。

答案 2 :(得分:6)

现在以推特形式:

sub prod { reduce { [ map { my $i = $_; map [ @$_, $i ], @$a } @$b ] } [[]], @_ }

use strict;
use warnings;
use List::Util qw(reduce);

sub cartesian_product {
  reduce {
    [ map {
      my $item = $_;
      map [ @$_, $item ], @$a
    } @$b ]
  } [[]], @_
}

答案 3 :(得分:6)

如果您愿意,可以使用我的Set::CrossProduct模块。您不必遍历整个空间,因为它会为您提供迭代器,因此您可以控制它。

答案 4 :(得分:0)

如果

  • 您不想包含依赖项
  • 你有少量阵列
  • 你的数组并不是很大

然后你可以这样做:

对于两个数组@xs@ys

map{ my $x = $_; map { [$x, $_] } @ys } @xs

对于三个数组@xs@ys@zs

map{ my $x = $_; map { my $y = $_; map { [$x, $y, $_] } @zs } @ys } @xs

答案 5 :(得分:0)

这是我的解决方案,它不需要任何模块,并且可以根据需要使用任意数量的集合。

sub set_product {
    my @array_of_aref = @_;
    if (@array_of_aref == 0) {
        return;
    }
    elsif (@array_of_aref == 1) {
        return $array_of_aref[0];
    }
    elsif (@array_of_aref >= 2) {
        my $array_a = shift @array_of_aref;
        my $array_b = shift @array_of_aref;
        my @array_c;
        foreach my $a ($array_a->@*) {
            foreach my $b ($array_b->@*) {
                if (ref $a eq "" and ref $b eq "") {
                    push @array_c, [$a,     $b];
                }
                elsif (ref $a eq "ARRAY" and ref $b eq "") {
                    push @array_c, [$a->@*, $b];
                }
                elsif (ref $a eq "" and ref $b eq "ARRAY") {
                    push @array_c, [$a,     $b->@*];
                }
                elsif (ref $a eq "ARRAY" and ref $b eq "ARRAY") {
                    push @array_c, [$a->@*, $b->@*];
                }
            }
        }
        while (my $aref = shift @array_of_aref) {
            @array_c = set_product(\@array_c, $aref);
        }
        return @array_c;
    }
}

示例:

    print $_->@* foreach set_product(["a","b"]);
    print $_->@* foreach set_product(["a","b"], [1,2,3]);
    print $_->@* foreach set_product(["a","b"], [1,2,3], ["x","y"]);
    print $_->@* foreach set_product(["a","b"], [1,2,3], ["x","y"], ["E","F"]);