13

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

如何得到:

[“大”、“红”、“苹果”]
[“大”、“红”、“梨”]

..ETC..

[“小”、“绿”、“香蕉”]

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

4

6 回答 6

16

这实际上不是排列,而是笛卡尔积。请参阅数学::笛卡尔::产品

#!/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
大红苹果
大红梨
大红香蕉
大黄苹果
大黄梨
大黄香蕉
大青苹果
大绿梨
大绿香蕉
小小的红苹果
小红梨
小红香蕉
小黄苹果
小黄梨
小黄香蕉
小青苹果
小绿梨
小绿香蕉
小红苹果
小红梨
小红香蕉
小黄苹果
小黄梨
小黄香蕉
小青苹果
小绿梨
小绿香蕉
于 2010-03-16T18:40:04.913 回答
8

现在以推特形式:

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 ]
  } [[]], @_
}
于 2010-03-16T20:37:20.440 回答
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])(三个数组引用的列表)到上一层递归。在该级别$last是对包含的数组的引用[4, 5, 6]

然后外部地图的主体运行 3 次,$_设置为[1],然后[2],最后[3]。然后,内部映射(4, 5, 6)在外部映射的每次迭代中运行,这将返回([1, 4], [1, 5], [1, 6]),([2, 4], [2, 5], [2, 6])和 finally ([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 上发布了一个问题,要求有人向我解释这一点。

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

于 2010-03-16T18:38:55.540 回答
6

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

于 2010-03-16T21:11:14.747 回答
1

如果

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

那么你可以简单地这样做:

对于两个数组@xs@ys

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

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

map{ my $x = $_; map { my $y = $_; map { [$x, $y, $_] } @zs } @ys } @xs
于 2015-10-29T17:15:46.460 回答
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"]);
于 2021-01-26T18:14:52.187 回答