4

I want to build several hashes using the same keys and for the keys to have the same order when I print them. So, in the example below, the keys of $hash1 and $hash2 should always have the same order, but there should be no need to keep that order when creating the hash.

use Data::Dumper;

my $hash1 = {
  keyc => 2,
  key1 => 1,
  keya => 3,
  keyb => 4,
};

my $hash2 = {
  keyc => 2,
  key1 => 1,
  keya => 3,
  keyb => 4,
};

print Dumper $hash1, $hash2;

But the output is as follows:

$VAR1 = {
          'key1' => 1,
          'keyc' => 2,
          'keyb' => 4,
          'keya' => 3
        };
$VAR2 = {
          'keyb' => 4,
          'keya' => 3,
          'keyc' => 2,
          'key1' => 1
        };

i.e the hashes have a different and unexpected order. What's wrong with my perl?

My perl version is:

This is perl 5, version 18, subversion 2 (v5.18.2) built for darwin-thread-multi-2level
(with 2 registered patches, see perl -V for more detail)

Notice: I know that keys of perl hash is unsorted order. I want they have the same order, but there should be no need to have the sorted order. I hope that I can get the same print output if I run the code again.

Following advice from answers, I set two environment variables:

PERL_HASH_SEED=0x00 PERL_PERTURB_KEYS=0

Then I can get the same output when I run the code repeatedly.

G. Cito
  • 6,210
  • 3
  • 29
  • 42
lutaoact
  • 4,149
  • 6
  • 29
  • 41
  • 5
    Nothing is wrong. Hashes are inherently not ordered. – squiguy May 20 '15 at 04:02
  • You would set `PERL_HASH_SEED` and use `PERL_PERTURB_KEYS=0` If you want the keys to stay in their *insertion order* (ordered but not sorted). This was not clear in your question. If you are serious about maintaining the order *in the application* (rather than relying on an environment variable) you would be better off tieing the hash with one of the CPAN modules that were mentioned. – G. Cito May 21 '15 at 13:00

3 Answers3

12

When printing a hash there are a few different notions of order that are relevant: "insertion order", "sort order" and "random". See the ENVIRONMENT section of the perlrun documentation for a discussion of ways you can control this behavior and for the reasons why the default is to use hash randomization.

For at least a decade hashes in perl have not guaranteed key order. More recently, hash randomization has been part of a general security "hardening" effort. There are good reasons for hashes to be randomized. For more details see the perlsec discussion of algorithmic complexity attacks. You'll note in the Perl security documentation that further enhancements were added in perl-5.18 - if you are seeing a different behavior compared to previous versions it may be due to these most recent changes.

Besides explicitly sorting your hash keys in a deterministic way, there are other approaches you can take to ordering your hashes: Hash::Ordered is one example. The Hash::Ordered documentation has a good discussion of the pros and cons of a number of other modules.

While a hash is an "unordered basket" of scalars arranged in key-value pairs; an array is an "ordered sequence" of scalars [1]. A "slice" is way of accessing "several elements of a list, an array, or a hash simultaneously". A slice uses the @ sigil since the operation returns a list of multiple values - and with @ we get "ordered sequence". The upshot is that one way to impose a kind of "order" on a hash is by using a slice to access it:

# We want alphabetical disorder ...
my %hashed = ( 1 => "z", 2 => "x", 3 => "y" );
for my $key ( keys %hashed ) { print $hashed{$key} } ;
__END__    
zyx

We want "zxy" not "zyx". To impose our arbitrary version of order on this hash we first need to recognize that culprit here is keys %hashed which returns the keys in random order. The solution is to sort keys of ccurse and in this contrived example we store them in @sort_order and use it to "slice" out what we want from the hash, the way we want it:

my @sort_order = sort keys %hashed ;
print @hashed{@sort_order} ;
__END__
zxy

Tada!! Slices can be useful when you want to store keys and values in a hash but access that data in an ordered way. Remember the "@" when you want to slice a hash; as perldata puts it: "you use an '@' ... on a hash slice ... [because] you are getting back ...a list". And lists are orderly.


[1] The definitions of hashes as "unordered baskets" and arrays as "ordered sequence" are from Mike Friedman's (FRIEDO) excellent article on Arrays vs. Lists in Perl.

Further References

G. Cito
  • 6,210
  • 3
  • 29
  • 42
  • 1
    I'd suggest adding a reference to hash slicing. E.g. `@hash{@element_order}` as I find that useful. – Sobrique May 20 '15 at 14:21
  • @Sobrique thanks ... I'm not sure if it is "idiomatic" but I sometimes store ready-to-use arrays to use for slicing ... – G. Cito May 20 '15 at 14:25
  • @lutaoact - even more fun awaits with `perl-5.20` where you have the option to access a hash with `%hash{ @keys }` and get back a ready-made subset of the hash with [key/value slices](http://www.effectiveperlprogramming.com/2014/07/perl-5-20-introduces-keyvalue-slices/). – G. Cito May 20 '15 at 17:13
  • @mob thanks for edits on the `{` to `(` ... I dithered on whether to use a anonymous hash reference and forgot to double check the `% ()` and `$ ()` ... – G. Cito Jun 02 '15 at 21:00
4

Nothing wrong with your perl, a hash is unsorted.

If you want to sort by key you need to do something like that:

foreach my $key (sort keys %hash1) {
    print $key, $hash1{$key};
}

and same thing for hash2...

Gonen
  • 4,005
  • 1
  • 31
  • 39
nbeck
  • 159
  • 6
3

G. Cito's answer is correct. If you want sorted output from Data::Dumper however, you can do:

use Data::Dumper;

my $hash1 = {
  keyc => 2,
  key1 => 1,
  keya => 3,
  keyb => 4,
};

my $hash2 = {
  keyc => 2,
  key1 => 1,
  keya => 3,
  keyb => 4,
};

my $dumper = Data::Dumper->new([$hash1, $hash2]);
$dumper->Sortkeys(1);
print $dumper->Dump;
salparadise
  • 5,699
  • 1
  • 26
  • 32
  • You can also set this as a variable to change the behavior of `print Dumper()` and friends. e.g. `$Data::Dumper::Sortkeys = 1`; – G. Cito May 20 '15 at 13:45
  • 1
    I'd argue that if you're trying to sort it, then you don't want to be using `Dumper` in the first place. – Sobrique May 20 '15 at 14:19
  • Data::Dumper is fine for sorting. Use the `$Data::Dumper::Sortkeys` feature. – brian d foy Aug 28 '23 at 20:29