【问题标题】:How do I get all possible combinations (not permutations) from two different arrays in Perl?如何从 Perl 中的两个不同数组中获取所有可能的组合(不是排列)?
【发布时间】:2021-09-20 17:21:40
【问题描述】:

假设我有这两个数组:

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 和相关模块,但我只能看到它们用于从 single 数组中获取元素组合的示例。

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

编辑: 为了完整起见,我有许多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.
    }
  ]
)

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

【问题讨论】:

  • “比嵌套循环更简单”?你的意思是比for my $varA (@varA) { for my $varB (@varB) { print "varA = $varA, varB = $varB" } 更简单?我认为您将很难找到比这更简单的东西。
  • @TLP 当然,我的例子很简单。我的真实代码有 10-15 个级别,每个级别有许多可选值。这是产品问题。为了便于解释,我将其分解。
  • 你想要什么样的输出——像(a1, b1, a2, b2)这样的组合值列表,或者像你显示的那样由两者组成的一些字符串......?
  • @skeetastax 好吧,尽量不要简化太多。我们在这里获得各种技能水平的问题。

标签: arrays perl variables combinations element


【解决方案1】:

这是您似乎在询问的Cartesian product

有一些库,比如Set::CrossProductMath::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;

可以使用任何其他方法来代替笛卡尔积, 并根据每个组合构建该 hashref。

上面的代码打印出来

[ { 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,它还提供了cartesian 功能。该模块十年未曾被触及,但它都是纯 Perl,因此您可以使用 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 返回一个带有arrayrefs 的数组,每个数组都有一个组合中的元素,所有组合中的块{...} 返回true。 (块只是一个过滤器,但在语法中是强制性的。)

然后通过map 传递给所需的输出格式。

【讨论】:

  • @skeetastax 可以与每个配对的成员(一个来自@ary1 另一个来自@ary2)做任何事情,无论你想要什么。都带有map 和模块中的函数。你能解释一下你到底需要什么吗?我不明白该评论(请参阅我的回复)。在问题中添加示例? (只需从问题中的这两个数组中添加您需要的精确输出。)
  • @skeetastax 啊,谢谢!我明白了……现在就写出来
  • @skeetastax 已发布...现在将进一步编辑,但这应该已经是它了吗?
  • @skeetastax 哼?哦 :( 我会用另一种工具修改答案,等我一分钟......
  • @skeetastax 添加了提到的其他两个库的使用示例
【解决方案2】:
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;";
   }
);

【讨论】:

  • 谢谢@ikegami。我可以想象生成散列数组,每个散列包含一个 key=value 对,对我的所有键执行此操作(这样我每个命名键有 1 个数组),然后在 AoH 上运行上述内容,对吗?我认为这会给我key=value 对的独特组合,不是吗?
  • 你可以用 $varA 和 $varB 做任何你想做的事情
猜你喜欢
  • 2019-05-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-07
  • 1970-01-01
相关资源
最近更新 更多