Perl:具有group_by函数的模块,用于将hashrefs数组转换为arrayrefs的哈希

Perl:具有group_by函数的模块,用于将hashrefs数组转换为arrayrefs的哈希,perl,Perl,给定一个hashref数组: my @items = ( { key => 'a-key', value => 'a-value-1', }, { key => 'a-key', value => 'a-value-2', }, { key => 'b-key', value => 'b-value-1',

给定一个hashref数组:

my @items = (
    {
        key   => 'a-key',
        value => 'a-value-1',
    },
    {
        key   => 'a-key',
        value => 'a-value-2',
    },
    {
        key   => 'b-key',
        value => 'b-value-1',
    },
    {
        key   => 'b-key',
        value => 'b-value-2',
    },
);
我想用存储在arrayrefs中的按键分组的值生成一个哈希:

my %grouped = (
    'a-key' => ['a-value-1', 'a-value-2'],
    'b-key' => ['b-value-1', 'b-value-2'],
);
我知道这可以通过在元素上循环实现:

my %grouped;
for my $item (@items) {
    push @{ $grouped{$item->{key}} }, $item->{value};
}
但这似乎是一个足够普遍的公式,有一个模块可以提供类似于or的group by函数的函数。Perl中是否有提供类似功能的常用库

预计会有人提到
List::Util::reduce
,对我来说,这似乎并不比原生方式简单,而且使用它生成hashref似乎很尴尬

my $grouped = reduce {
    push @{ $a->{$b->{key}} }, $b->{value};
    $a;
} {}, @items;
我在想象这样的用法:

my %grouped = group_by { $_->{key} } @items;
for my $arrayref (values %grouped) {
    $_ = $_->{value} for @$arrayref;
}
编辑:我刚刚意识到,如果我们使用上面的
group_by
,我们仍然需要进一步处理结果,比如:

my %grouped = group_by { $_->{key} } @items;
for my $arrayref (values %grouped) {
    $_ = $_->{value} for @$arrayref;
}

这似乎没有一个库函数,所以我自己写了一个

sub group_by (&@) {
    my ($get_kv, @items) = @_;

    my %groups;
    for (@items) {
        my ($k, $v) = ($get_kv->(), $_);
        push @{ $groups{$k} }, $v;
    }
    %groups;
}
在块内部,第一个元素指定分组依据的键,第二个元素可选地指定要分组的值。如果未提供第二个元素,则将整个项用作值

用法:

my %keys_to_values = group_by { $_->{key}, $_->{value} } @items;
# (
#     'a-key' => [qw(a-value-1 a-value-2)],
#     'b-key' => [qw(b-value-1 b-value-2)],
# )

my %keys_to_items = group_by { $_->{key} } @items;
# (
#    'a-key' => [
#        {
#            key   => 'a-key',
#            value => 'a-value-1',
#        },
#        {
#            key   => 'a-key',
#            value => 'a-value-2',
#        },
#    ],
#    'b-key' => [
#        {
#            key   => 'b-key',
#            value => 'b-value-1',
#        },
#        {
#            key   => 'b-key',
#            value => 'b-value-2',
#        },
#    ],
# )

这似乎没有一个库函数,所以我自己写了一个

sub group_by (&@) {
    my ($get_kv, @items) = @_;

    my %groups;
    for (@items) {
        my ($k, $v) = ($get_kv->(), $_);
        push @{ $groups{$k} }, $v;
    }
    %groups;
}
在块内部,第一个元素指定分组依据的键,第二个元素可选地指定要分组的值。如果未提供第二个元素,则将整个项用作值

用法:

my %keys_to_values = group_by { $_->{key}, $_->{value} } @items;
# (
#     'a-key' => [qw(a-value-1 a-value-2)],
#     'b-key' => [qw(b-value-1 b-value-2)],
# )

my %keys_to_items = group_by { $_->{key} } @items;
# (
#    'a-key' => [
#        {
#            key   => 'a-key',
#            value => 'a-value-1',
#        },
#        {
#            key   => 'a-key',
#            value => 'a-value-2',
#        },
#    ],
#    'b-key' => [
#        {
#            key   => 'b-key',
#            value => 'b-value-1',
#        },
#        {
#            key   => 'b-key',
#            value => 'b-value-2',
#        },
#    ],
# )

选择是使用已知语法的单行代码,而不是在新模块中学习新api。IMHO,perl的优势在于语法非常灵活,日常任务不需要模块。@BOC好吧,它是4行,不是一行。;-)我想你是说你更喜欢
for
方法而不是
reduce
?实际上,正是由于一位同事对
for
reduce
的看法不同,我才发布了这个问题。我原以为
groupby
是一个很好的折衷方案,但由于意识到它并不简单,我又开始喜欢简单循环了。@AdamMillerchip有了以上的评论,我急忙说我更喜欢循环而不是
reduce
。(我不知道有哪一个模块具有这样的特定功能。)如果有必要(
push…for@items
),+声明,也可以在一行中编写循环。或者把它放在一个sub里,如果有更多的话,它可能会变成一个小的实用模块。(甚至可以设置相同的接口
group_by{}LIST
)。选择是使用已知语法的单行代码,而不是在新模块中学习新api。IMHO,perl的优势在于语法非常灵活,日常任务不需要模块。@BOC好吧,它是4行,不是一行。;-)我想你是说你更喜欢
for
方法而不是
reduce
?实际上,正是由于一位同事对
for
reduce
的看法不同,我才发布了这个问题。我原以为
groupby
是一个很好的折衷方案,但由于意识到它并不简单,我又开始喜欢简单循环了。@AdamMillerchip有了以上的评论,我急忙说我更喜欢循环而不是
reduce
。(我不知道有哪一个模块具有这样的特定功能。)如果有必要(
push…for@items
),+声明,也可以在一行中编写循环。或者把它放在一个sub里,如果有更多的话,它可能会变成一个小的实用模块。(甚至可以设置相同的接口
groupby{}LIST
)。在“块可以返回可选的第二个值”中,您的意思是说它可以“获取”可选的第二个值(而不是“返回”)?我想这取决于您的视角。从打电话的人的角度来看,也许“take”是一个更好的选择。从执行的角度来看,块是一个子例程,我们在列表上下文中调用它,使用返回列表的一个或两个元素。“如果一个列表被提供给块,第一个元素指定要分组的密钥,第二个元素指定将被分组的值。如果没有提供第二个元素,则整个项目被用作值。”当然,好点(但是人们通常从用户的角度考虑接口)。因为它可能会误导一个偶然的阅读更多的解释可能确实是一个好主意,你给出的是准确和完全清楚的。这个接口(带有代码块)的一个优点是,如果需要,您可以在以后轻松地调整和扩展功能(我想它不会发布)。更不用说,如果您需要更多类似的自定义处理,它是一个很好的“入门”模块。更新了说明。谢谢你的反馈。很好。在“块可以返回可选的第二个值”中,您的意思是说它可以“获取”可选的第二个值(而不是“返回”)?我想这取决于您的视角。从打电话的人的角度来看,也许“take”是一个更好的选择。从执行的角度来看,块是一个子例程,我们在列表上下文中调用它,使用返回列表的一个或两个元素。“如果一个列表被提供给块,第一个元素指定要分组的密钥,第二个元素指定将被分组的值。如果没有提供第二个元素,则整个项目被用作值。”当然,好点(但是人们通常从用户的角度考虑接口)。因为它可能会误导一个偶然的阅读更多的解释可能确实是一个好主意,你给出的是准确和完全清楚的。这个接口(带有代码块)的一个优点是,如果需要,您可以在以后轻松地调整和扩展功能(我想它不会发布)。更不用说,如果您需要更多类似的自定义处理,它是一个很好的“入门”模块。更新了说明。谢谢你的反馈