我该如何在Perl中按键对哈希表进行排序?

6
我想对一个哈希表进行排序,该哈希表的值实际上是一个哈希表。例如:
my %hash1=(
   field1=>"",
   field2=>"",
   count=>0,
);
my %hash2;
$hash2{"asd"}={%hash1};

我向%hash2插入了许多不同计数值的哈希。

如何根据hash1的计数值对%hash1进行排序?

是否有一种方法可以在不手动实现快速排序的情况下完成此操作,例如使用Perl的sort函数?


你的意思是想要根据hash2中的值的计数,获取哈希列表(如hash1)的排序列表吗? - Jagmal
是的,Jagmal,这意味着我想根据 $hash2{"asd"}{count} 进行排序。 - systemsfault
5个回答

10
my @hash1s = sort {$a->{count} <=> $b->{count}} values %hash2;

我尝试了那种方式,但是收到了以下警告: [警告]: 在数字比较 (<=>) 中使用未初始化的值。 - systemsfault
当我测试时,无论是我的答案还是对你的答案的评论,代码片段都能正常工作(即使使用了-w和"use strict")。 - C. K. Young
你收到这个警告是因为你的一个或多个哈希没有为 'count' 键设置值。如果你想将它们视为 0 进行计数,你可以执行以下操作:sort {($a->{count} || 0) <=> ($b->{count} ||0)} values %hash2; - nohat
所有我的计数值都有0作为默认值。但在我的生产代码中,键的名称不是count,而是total_cnt,但排序算法按升序排序,我需要按降序排序。 - systemsfault
1
@holydiver:使用 $b->{count} <=> $a->{count} 实现降序排列(即交换 a 和 b)。 - C. K. Young
好的,我使用 $b->{count} <=> $a->{count} 按降序排序了。谢谢大家 :D。 - systemsfault

6
perlfaq4中,关于如何对哈希进行排序(可选择按值而不是键)的问题的答案包含了你组合代码所需的大部分信息。
你可能还想看一下《学习Perl》中关于排序的章节。
Chris给出了一个完全正确的答案,尽管我不喜欢使用values。一个更常见的做法是遍历顶层哈希的键,但按照二级键进行排序:
my @sorted_hashes = 
    sort { $hash2->{$a}{count} <=> $hash2->{$b}{count} } 
    keys %hash2;

我这样做是因为这样稍微不那么费脑筋。

如何对哈希进行排序(可选择按值而非键排序)?

(由brian d foy贡献)

要对哈希进行排序,从键开始。在此示例中,我们将键的列表提供给sort函数,它会对它们进行ASCIIbetical比较(可能受到区域设置的影响)。输出列表按ASCIIbetical顺序排列键。一旦我们有了键,我们可以遍历它们以创建一个报告,该报告按ASCIIbetical顺序列出键。

my @keys = sort { $a cmp $b } keys %hash;

foreach my $key ( @keys )
    {
    printf "%-20s %6d\n", $key, $hash{$key};
    }

虽然在sort()块中我们可以更加复杂一些。与其比较键,我们可以使用它们计算一个值,并将该值用作比较。

例如,为了使我们的报告排序不区分大小写,我们可以在双引号字符串中使用\L序列将所有内容转换为小写。然后,sort()块会比较小写值,以确定按照什么顺序放置键。

my @keys = sort { "\L$a" cmp "\L$b" } keys %hash;

注意:如果计算开销大或哈希包含许多元素,您可能需要考虑使用Schwartzian变换来缓存计算结果。

如果我们想按哈希值进行排序,我们使用哈希键来查找它。我们仍然会得到一个键的列表,但这次是按照它们的值进行排序。

my @keys = sort { $hash{$a} <=> $hash{$b} } keys %hash;

从那里开始,我们可以变得更加复杂。如果哈希值相同,我们可以在哈希键上提供二次排序。

my @keys = sort {
    $hash{$a} <=> $hash{$b}
        or
    "\L$a" cmp "\L$b"
    } keys %hash;

1
如果您想要按照hash2中的值的计数对哈希列表(如hash1)进行排序,可以尝试以下方法:
@sorted_hash1_list = sort sort_hash_by_count_key($a, $b) (values (%hash2);


# This method can have any logic you want
sub sort_hash_by_count_key {
    my ($a, $b) = @_;
    return $a->{count} <=> $b->{count};
}

你是不是想说 "sort &sort_hash_by_count_key, values %hash2" 而不是当前的代码? - C. K. Young

0

请参考http://perldoc.perl.org/functions/sort.html,了解Perl中sort函数的工作原理。

以下是一个示例,尽可能易读,不要过于追求Perl风格。

#!/usr/bin/perl
# Sort Hash of Hashes by sub-hash's element count.
use warnings;
use strict;


my $hash= {
            A=>{C=>"D",0=>"r",T=>"q"}
           ,B=>{}
           ,C=>{E=>"F",G=>"H"}
          };

sub compareHashKeys {0+(keys %{$hash->{$a}}) <=> 0+(keys %{$hash->{$b}}) }

my @SortedKeys = sort compareHashKeys keys %{$hash};
print join ("," , @SortedKeys) ."\n";

0+ 的作用是将值强制转换为数字,但 <=> 已经实现了这个功能,所以 0+ 真的是多余的。:-P - C. K. Young
好的,cmp或<=>返回的值已经是数字,分别为-1、0或1。数据是什么并不重要。sort()需要-1、0或1来决定如何排序。 - brian d foy
正如Chris所指出的,我在那里加了0+来将keys数组强制转换为标量(从而保持数组的长度)。显然<=>已经做到了这一点..我会继续这样做,因为这让我在重新阅读代码时明确了预期发生的情况。你让我笑翻了。 - lexu

0

如果要按数字排序,请使用<=>,如果要按字符串排序,请使用cmp。

# sort by the numeric count field on inner hash
#
foreach my $key (sort {$hash2{$a}->{'count'} <=> $hash2{$b}->{'count'}} keys %hash2) {
   print $key,$hash2{$key}->{'count'},"\n";
}

# sort by the string field1 (or field2) on the inner hash
#
foreach my $key (sort {$hash2{$a}->{'field1'} cmp $hash2{$b}->{'field1'}} keys %hash2) {
   print $key,$hash2{$key}->{'field1'},"\n";
}

要反转顺序,只需交换 $a 和 $b:

# sort by the numeric count field on inner hash
#
foreach my $key (sort {$hash2{$a}->{'count'} <=> $hash2{$b}->{'count'}} keys %hash2) {
   print $key,$hash2{$key}->{'count'},"\n";
}

# sort by the string field1 (or field2) on the inner hash
#
foreach my $key (sort {$hash2{$a}->{'field1'} cmp $hash2{$b}->{'field1'}} keys %hash2) {
   print $key,$hash2{$key}->{'field1'},"\n";
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接