How can I maintain the order of keys I add to a Perl hash?

你。 提交于 2019-11-27 08:38:26

Hashes are not ordered, but as usual, CPAN offers a solution: 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"; 
}

Data in a hash table is stored in order of the keys' hash code, which for most purposes is like a random order. You also want to store the order of the first appearance of each key. Here's one way to approach this problem:

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";
}

From perlfaq4's answer to "How can I make my hash remember the order I put elements into it?"


How can I make my hash remember the order I put elements into it?

Use the Tie::IxHash from CPAN.

use Tie::IxHash;

tie my %myhash, 'Tie::IxHash';

for (my $i=0; $i<20; $i++) {
    $myhash{$i} = 2*$i;
    }

my @keys = keys %myhash;
# @keys = (0,1,2,3,...)

Simply:

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

Or as oneliner

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

Another option is David Golden's (@xdg) simple pure perl Hash::Ordered module. You gain order but it is slower since the hash becomes an object behind the scenes and you use methods for accessing and modifying hash elements.

There are probably benchmarks that can quantify just how much slower the module is than regular hashes but it's a cool way to work with key/value data structures in small scripts and fast enough for me in that sort of application. The documentation mentions several other approaches to ordering a hash as well.

piCookie

I'm not convinced that this is always a better technique, but I have used it sometimes. Instead of just having the "seen" type of hash, it can store both the count and order noticed.

Basically, instead of $count{$line} having the number of times seen, $count{$line}{count} is the times seen and $count{$line}{order} is the order in which it was seen.

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";
}

Hashes are just arrays until they're assigned in Perl, so if you cast it as an array, you can iterate over it in its original order:

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
}

You lose the benefits of hash indexing if you do that obviously. But since a hash is just an array, you can create one just by assigning the array to a hash:

my %hash = @array;
say $hash{z};

This is maybe just a variation on the "use an array as an index" solution, but I think it's neater because instead of typing out your index manually (or in some other way), you're creating it directly from the source array.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!