Sprad has asked for the wisdom of the Perl Monks concerning the following question:

We all know that you can't rely on the order that hash items are stored in. But can you tweak things so you can?

I'd like to be able to have a hash variable, put things in it, then later read them back out in the same order I wrote them. I could sorta do it with a pair of arrays and an index variable, but I like being able to reference an element by its key.

What are some ways I could accomplish this?

---
A fair fight is a sign of poor planning.

Replies are listed 'Best First'.
Re: Ordered Hashes
by liz (Monsignor) on Nov 14, 2003 at 23:16 UTC
      Why the question mark? Tie::IxHash is the canonical module for this kind of job.

      As an interesting sidenote to the OP: (s)he pretty much described how it works:

      I could sorta do it with a pair of arrays and an index variable, but I like being able to reference an element by its key.
      Well: the object behind the tied hash comprises of a pair of arrays, one for the keys and one for the values, and a hash, which points towards the index in these arrays for any key.

      An alternative implementation, which would likely be a little more compact, would be to use just one hash and one array. For example, the hash could work in the normal way, and the array would contains the ordered keys. But there are other schemes.

Re: Ordered Hashes
by pg (Canon) on Nov 14, 2003 at 23:20 UTC

    Take a look at Tie::IxHash. In case you are interested in doing something by yourself, I did some before:

    package OrderedHash; use strict; sub new { my $self = {}; $self->{INDEX} = {}; $self->{PAIRS} = []; bless $self; return $self; } sub set { my ($self, $key, $value) = @_; push @{$self->{PAIRS}}, $key; $self->{INDEX}->{$key} = $#{$self->{PAIRS}}; push @{$self->{PAIRS}}, $value; } sub get { my ($self, $key) = @_; return $self->{PAIRS}->[$self->{INDEX}->{$key} + 1]; } sub list_in_order { my $self = shift; my $is_key = 1; foreach (@{$self->{PAIRS}}) { if (!$is_key) { print "$_\n"; } else { print "[$_] = "; } $is_key = !$is_key; } } 1; use OrderedHash; use Data::Dumper; use strict; my $oh = new OrderedHash; $oh->set("a", 1); $oh->set("b", 2); $oh->set("c", 3); print Dumper($oh); $oh->list_in_order;
Re: Ordered Hashes
by davido (Cardinal) on Nov 15, 2003 at 05:39 UTC
    Others have mentioned the Tie::IxHash method for causing a hash to remember its insertion order. But I just wanted to mention what hasn't already been said: a Tie::IxHash should only be done after careful consideration of the fact that this approach carries with it some positives, and some negatives.

    If you think of your consideration process as a balance sheet, on the positive side, you get an ordered hash. On the negative side, you get a less speed efficient and less memory efficient hash. You trade speed and memory for convenience. That may be a worthwhile trade for you. Or it might be a dealbreaker. Just as long as you're aware of the issues, you can make the right decision for your particular situation.


    Dave


    "If I had my life to live over again, I'd be a plumber." -- Albert Einstein
Re: Ordered Hashes
by jeffa (Bishop) on Nov 15, 2003 at 16:52 UTC

    "I could sorta do it with a pair of arrays and an index variable, but I like being able to reference an element by its key."

    Liking and having are two different things ... if you really need to do so, then Tie::IxHash will do the trick. But if you don't, then maybe you simply need to rethink your datastructure. For example, you can store a list of hashes in an array:
    my @people = ( { name => 'John Doe', age => 30 }, { name => 'Sally Smith', age => 42 }, );
    Now the order is preserved, but you can't perform a O(1) lookup anymore ... but do you really need to? For the record, i have yet to need Tie::IxHash, but them's just my needs. ;)

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    B--B--B--B--B--B--B--B--
    H---H---H---H---H---H---
    (the triplet paradiddle with high-hat)
    
Re: Ordered Hashes
by Roger (Parson) on Nov 15, 2003 at 22:10 UTC
      We all know that you can't rely on the order that hash items are stored in.

    I just want to point out that unless you are using Perl 5.8.1 or above, the order of hash is 'predictable' and stays the same for the same set of input data.

    There was a previous discussion node 304286 on the order of hash, in which I explained why the order of hash is not random.