如何从 Perl 中的两个不同数组中获取所有可能的组合(不是排列)?

How do I get all possible combinations (not permutations) from two different arrays in Perl?

假设我有这两个数组:

my @varA = ("a","b"); # all possible values of varA
my @varB = (1,2); # all possible values of varB

我想得到输出变量组合:

varA = a, varB = 1; # the first possible combination
varA = a, varB = 2;
varA = b, varB = 1;
varA = b, varB = 2; # the last possible combination

我查看了 Algorithm::Combinatorics 和相关模块,但我只能看到它们用于从 单个 数组中获取元素组合的示例。

有比嵌套循环更简单的方法吗?

编辑: 为了完整起见,我有许多 keys 命名变量,它们可以采用许多可能值之一。我需要生成每个可能的 key=value 对的所有组合(不重复相同的键 - 每个组合只有唯一的键)。

假设我有以下属性键:

ShoeSize # can be one of a set of size values
CurrentAge # can be any integer from 0 up to, say, 110 years
HeightInCm # can be any integer up to, say, 200 (2m tall!)

现在假设我正在研究统计数据,我想生成 ShoeSize、HeightInCm 和 CurrentAge 的每个组合,然后开始计算有多少人匹配每个值组合(尽管忽略计数位 - 这只是为了这个插图 - 我的每个真实组合只有一个实例)。

我将每个组合存储为数组中的“3 键散列”(引用,即每个引用都指向相同匿名散列的实例 set) .

示例输出:

@AoH = (
  [ # element 0
    {
      ShoeSize=10,
      CurrentAge=14,
      HeightInCm=150
    }
  ],
  [ # element 1
    {
      ShoeSize=12,
      CurrentAge=23,
      HeightInCm=172
    }
  ],
  [ # element 2
    {
      ShoeSize=8,
      CurrentAge=64,
      HeightInCm=167
    }
  ],
  [ # element 3
    {
      etc.
    }
  ]
)

如何使用正确的唯一键集生成这个组合哈希数组才是真正的问题。

您似乎要问的是 Cartesian product

有一些库,比如 Set::CrossProduct and Math::Cartesian::Product. 第一个提供了一个生成器,可以以多种方式使用,第二个生成一次显示整个结果列表,可以对其进行过滤。

我不清楚您想如何组合两个集合(列表)中的元素 -- 连接值?形成元组?字符串?请澄清,如果需要,我可以添加具体示例。

另请注意,原则上可以只是双 map 或类似

my @cp = map { my $e = $_; map { $_ . $e } @ary2 } @ary1;

(这里的元素是串联的)


编辑 一个完整的示例已添加到问题中,以阐明问题。这是一种方法,使用一个模块来计算三个数组的叉积(三重 map 会有点难看)。

问题中描述的所需输出内置于可用的代码块中。

use warnings;
use strict;
use feature 'say';
use Data::Dump qw(dd);

use List::Gen qw(cartesian);

my @shoe_size = (8,10);     # (apologies to all "other" people...)
my @curr_age  = (21, 40);
my @height_cm = (170, 200);

# Returns a generator
my $gen = cartesian { 
    { shoe_size => $_[0], curr_age => $_[1], height_cm => $_[2] } 
} 
\@shoe_size, \@curr_age, \@height_cm;
    
my @combs = @$gen;  # Now this is an array of hashrefs

dd \@combs;

可以使用任何其他方法来形成笛卡尔积, 并从每个组合中构建哈希引用。

上面的代码打印

[
  { curr_age => 21, height_cm => 170, shoe_size => 8 },
  { curr_age => 21, height_cm => 200, shoe_size => 8 },
  { curr_age => 40, height_cm => 170, shoe_size => 8 },
  { curr_age => 40, height_cm => 200, shoe_size => 8 },
  { curr_age => 21, height_cm => 170, shoe_size => 10 },
  { curr_age => 21, height_cm => 200, shoe_size => 10 },
  { curr_age => 40, height_cm => 170, shoe_size => 10 },
  { curr_age => 40, height_cm => 200, shoe_size => 10 },
]

我将 hashrefs 直接放在一个数组中,但如果所需的输出确实需要每个都是 arrayref 的唯一元素,如问题所示,那很容易修改。

打印时hash(ref)元素的顺序可以随意排序。为简单起见,我为每个数据集使用普通数组,然后在输出中硬编码它们的名称;这可以通过适当地安排数据来避免。

一个明确的选择是对所有数据使用哈希,以便每个集合都有一个标签,然后我们可以为其提供排序顺序。这还有另一个好处:我们可以在程序的一个地方按照他们想要的顺序列出名字。让我知道示例是否有用。


还有更多。首先,如果您无论如何都可以使用高阶工具,那么一定要查看令人着迷的 List::Gen, which also provides a cartesian function. The module hasn't been touched in a decade but it's all pure Perl so you can use its source 以寻找感兴趣的东西。


使用其他工具构建产品的示例

Set::CrossProduct

use Data::Dump qw(dd);

use Set::CrossProduct; 
    
my $cp = Set::CrossProduct->new( { # can give them names
    shoe_size => \@shoe_size,
    curr_age  => \@curr_age,
    height_cm => \@height_cm
} );

# Generator ($cp object) is ready

my $combs = $cp->combinations;  # all combinations at once

dd $combs;

生成器也可用于一次迭代一个组合。它的生成器有一组精挑细选的小功能。

Math::Cartesian::Product

use Data::Dump qw(dd);

use Math::Cartesian::Product;

my @cp = map { 
        { shoe_size => $_->[0], curr_age => $_->[1], height_cm => $_->[2] } 
    }
    cartesian { 1 } \@shoe_size, \@curr_age, \@height_cm;

dd $_ for @cp;

函数 cartesian returns 一个带有 arrayrefs 的数组,每个数组都有组合中的元素,所有组合的块 {...} returns 为真。 (该块只是一个过滤器,但在语法中是强制性的。)

然后通过 map 进行格式化以获得所需的输出。

use Algorithm::Loops qw( NestedLoops );

my $iter = NestedLoops([ \@varA, \@varB ]);
while ( my ($varA, $varB) = $iter->() ) {
   say "varA = $varA, varB = $varB;";
}

use Algorithm::Loops qw( NestedLoops );

NestedLoops(
   [ \@varA, \@varB ],
   sub {
      my ($varA, $varB) = @_;
      say "varA = $varA, varB = $varB;";
   }
);