1

I have the following script

my %hash = (
             'ev2'  => 'aaaa',
             'ev1' => 'bbbb'
           );   

for my $key (sort keys %hash) {
   print $key , '-' , $hash{$key},  "\n";
} 

that prints

ev1 - bbbb
ev2 - aaaa

I want to print the elements in the order they were added to the hash.

ev2 - aaaa  
ev1 - bbbb

How can I do this?

ikegami
  • 367,544
  • 15
  • 269
  • 518
Paramore
  • 1,313
  • 2
  • 19
  • 37

3 Answers3

3

The order of the key/value pairs in the list you use to initialize the hash variable is not preserved; by the time the hash variable is populated, that ordering is long gone.

To preserve the order in which the keys were originally added to the hash - which includes the order of any list used to initialize it - you can use the CPAN module Hash::Ordered.

It has a tie interface, so you can use a Hash::Ordered object as if it were a plain hash. Note that you don't need to quote keys on the left side of => if they're simple words:

use Hash::Ordered;

tie my %hash, Hash::Ordered => (
             ev2 => 'aaaa',
             ev1 => 'bbbb'
           );

while (my ($key, $value) = each %hash) {
  print "$key - $value\n";
}

Output:

ev2 - aaaa
ev1 - bbbb

You could also do the loop as in your original code, just without the sort:

for my $key (keys %hash) {
  print "$key - $hash{$key}\n";
}

But using each saves a hash lookup for each item and returns them in the same order as keys.

@duskwuff suggested Hash::Ordered first; I just supplied some sample code. @ikegami suggested the older module Tie::IxHash. Both work for this purpose, and neither comes standard with Perl; you need to install them from the CPAN (e.g. with the cpan or cpanm commands).

Mark Reed
  • 91,912
  • 16
  • 138
  • 175
1

A hash does not record the order in which elements are added to it, so you can't achieve what you want with just a hash.

You could use Tie::IxHash to provide something that looks like a hash, but preserves insertion order.

use Tie::IxHash qw( );

tie my %hash, Tie::IxHash => (
    ev2 => 'aaaa',
    ev1 => 'bbbb',
);   

for my $key ( keys(%hash) ) {
    my $val = $hash{$key};
    ...
}

There will be a performance penalty for using Tie::IxHash or similar modules.


If you don't need to access arbitrary elements, it would be much faster to simply use an array.

use List::Util qw( pairs );    # 1.29+

my @pairs = (
    ev2 => 'aaaa',
    ev1 => 'bbbb',
);   

for (pairs(@pairs)) {
    my ($key, $val) = @$_;
    ...
}

or

for (my $i=0; $i<@pairs; ) {
    my $key = $pairs[$i++];
    my $val = $pairs[$i++];
    ...
}
ikegami
  • 367,544
  • 15
  • 269
  • 518
1

You can't, exactly. Hashes do not have a "natural" order -- the order that elements were assigned is not recorded.

If you really want an ordered hash, use Hash::Ordered. Or an array.