如何保持我添加到 Perl 哈希表中的键的顺序?

23
如何在以下程序中使用哈希计数后保持实际列表的顺序?例如,<DATA> 是什么意思?
a
b
e
a
c 
d 
a
c
d
b
etc.

使用哈希,我统计了每个元素的出现次数。
而我想要的是:
a  3
b  2
e  1
c  2
d  2

但是下面的程序告诉我不同的结果。
my (%count, $line, @array_1, @array_2);
while ($line = <DATA>) {
    $count{$line}++ if ( $line =~ /\S/ );
}
@array_1 = keys(%count);
@array_2 = values(%count);
for(my $i=0; $i<$#array_1; $i++)
{
   print "$array_1[$i]\t $array_2[$i]";
}

1
请您能否给您的代码打上标签,以便更容易地阅读它。 - Space
@Sinan:不是这样的……我找不到地方来感谢所有精彩的回答……只是想知道是通过添加评论还是其他地方来完成的……我对stackoverflow和perl都是新手…… 抱歉,也许我应该花些时间学习如何积极参与这个平台…… 几乎所有的答案都给了我一个新的学习思路……并且满足了我的需求…… - Cthar
1
@Cthar - 你应该选择最适合你问题的答案(还要考虑它的书写质量和易于理解程度),并通过点击问题旁边的勾选符号来接受它。你可以通过点赞来给其他好的答案加分。当然,积极的评论也是受欢迎的。这些做法有助于他人找到最佳答案,并奖励那些帮助你的人。 - harmic
我为了保持顺序所做的是在我的项目前加上一个数字...例如,如果我有成千上万个项目,第一个可能是“0001苹果酱”,然后是“0002土豚浆果”,如果我使用实际的条目名称,我知道我必须首先删除“^\d{4}\s(.+)”,并使用$1(假设苹果酱和土豚浆果是我首选的哈希键之一)。我从思科如何维护其EEM脚本排序中获得了这个想法,对于我的目的而言,它已经运作良好。 - SaintMaybe
实际上,上面的帖子中提到的想法听起来像我用过的另一种技术——我为哈希表条目的顺序保留了一个分配。然后,我需要反复迭代哈希表以填充一个有序列表,然后再引用键本身。维护关键名称列表还有其他用途,例如当您正在读取类似JSON的输入文件并希望填充与输入文件相同深度的哈希表时(您将具有动态深度的push / pop操作,根据指示符进行操作,例如哈希表中的括号,告诉您何时执行哪个操作)。 - SaintMaybe
7个回答

38

哈希表是无序的,但通常情况下,CPAN提供了一个解决方案:Tie::IxHash

use Tie::IxHash;
my %count;
tie %count, 'Tie::IxHash';

while ($line = <DATA>) {
$count{$line}++ if ( $line =~ /\S/ );
}

while( my( $key, $value)= each %count) {
    print "$key\t $value"; 
}

15

哈希表中的数据是按照键的哈希码顺序存储的,对于大多数情况而言,这就像是一种随机顺序。您还希望存储每个键第一次出现的顺序。以下是解决此问题的一种方法:

my (%count, $line, @display_order);
while ($line = <DATA>) {
    chomp $line;           # strip the \n off the end of $line
    if ($line =~ /\S/) {
        if ($count{$line}++ == 0) {
            # this is the first time we have seen the key "$line"
            push @display_order, $line;
        }
    }
}

# now @display_order holds the keys of %count, in the order of first appearance
foreach my $key (@display_order)
{
    print "$key\t $count{$key}\n";
}

4
在我看来,这比使用Tie::IxHash更好,因为我认为它超出了原帖作者的需求范围。 更适合使用键的显示顺序,就像这个答案中所示,或者使用foreach my $key (sort keys %count) { ... } - Ether

11

7

简单来说:

my (%count, @order);
while(<DATA>) {
  chomp;
  push @order, $_ unless $count{$_}++;
}
print "$_ $count{$_}\n" for @order;
__DATA__
a
b
e
a
c
d
a
c
d
b

或者作为单行代码

perl -nlE'$c{$_}++or$o[@o]=$_}{say"$_ $c{$_}"for@o'<<<$'a\nb\ne\na\nc\nd\na\nc\nd\nb'

5
另一个选项是David Golden(@xdg)的简单纯Perl Hash::Ordered 模块。虽然它会使哈希变成一个对象,你需要使用方法来访问和修改哈希元素,但它可以使哈希有序。可能有一些基准测试可以量化模块比常规哈希慢多少,但这是在小型脚本中处理键/值数据结构的很酷的方法,并且在那种应用程序中足够快。文档还提到了几种其他排序哈希的方法。

4
我并不确定这总是更好的技术,但我有时使用它。它不仅可以存储“已查看”类型的哈希,还可以存储注意到的计数和顺序。
基本上,$count{$line} 不再只表示出现次数,而是 $count{$line}{count} 表示出现次数,$count{$line}{order} 表示出现顺序。
my %count;
while (my $line = <DATA>) {
    chomp $line;
    if ($line =~ /\S/) {
        $count{$line} ||= { order => scalar(keys(%count)) };
        $count{$line}{count}++;
    }
}

for my $line (sort { $count{$a}{order} <=> $count{$b}{order} } keys %count ) {
    print "$line $count{$line}{count}\n";
}

1

在Perl中,哈希表只是数组,直到被赋值。因此,如果将其转换为数组,就可以按照原始顺序迭代它:

my @array = ( z => 6,
              a => 8,
              b => 4 );

for (my $i=0; $ar[$i]; ++$i) {
    next if $i % 2;
    my $key = $ar[$i];
    my $val = $ar[$i+1];

    say "$key: $val"; # in original order
}

如果你这样做,显然会失去哈希索引的好处。但由于哈希只是一个数组,你可以通过将数组分配给哈希来创建一个哈希:
my %hash = @array;
say $hash{z};

这可能只是“使用数组作为索引”解决方案的一种变化,但我认为它更加整洁,因为你不需要手动(或以其他方式)输入索引,而是直接从源数组创建它。

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