John M. Dlugosz has asked for the wisdom of the Perl Monks concerning the following question:

In a project of mine, I plan on making a "work list" of tuples available to a callback before the work is actually performed. This is naturally kept as a hash, so they can be looked-up by the left part, and still easy enough to process the whole list with each or keys.

However, I wonder if a derived class might want to know the order in which they were added, some day.

Originally I figured that the access method to get the worklist, used strictly internally also for all access to it, could be overridden to return a reference to a tied hash that also keeps an order. It turns out that this actually is a little more complex, because I would also need to provide a method to reset the list. So it's not as cute as I thought.

So I'm thinking why not just provide the order already? I could use such an ordered hash class from the getgo, but I don't see one as a standard module and I want to use only modules that come with the normal Perl installation. Also there might be a performance hit with that.

Or, I can keep a list for the order as well as the hash for the index, explicitly. So, how do I provide access to that?

Storing the ordering as a specially-named member of the hash seems awkward, and less than elegant from a module interface point of view.

Two separate named methods would be clear.

Having multiple return values from a single method would be neater, but would that be awkward too?

I'm open to suggestions and design musings.

—John

Replies are listed 'Best First'.
Re: Hashes made to order
by chromatic (Archbishop) on Dec 24, 2002 at 06:56 UTC
    However, I wonder if a derived class might ...
    So I'm thinking why not just provide the order already?

    I wouldn't add it. I try to avoid adding features that don't pay off immediately. It keeps my code smaller and cleaner, helps me hit release dates earlier, and prevents me from taking risks that don't pay off.

    If you wait until you actually need it, you'll be in a better position to design a good API. You'll also have a better idea what performance level you need to hit.

Re: Hashes made to order
by djantzen (Priest) on Dec 24, 2002 at 03:50 UTC

    It sounds like you've already considered and discarded this, but I'll suggest it anyway: IxHash.

    But a couple of other options that you didn't mention are:

    1. A normal associative array; that is, two parallel arrays manually kept in sync. It means you'd have to grep or foreach over the list to find your element, but you'd preserve order by pushing entries onto your worklist.

    2. A pseudo-hash. Everyone seems to hate them, and they won't be around in Perl 6, but they seem to do what you want. Basically, you'd do your key lookup in the first element of the array, which is a hash, and then use the returned array index to pull out the appropriate entry. You would likewise push elements onto the array to preserve order, but at the same time add the new entry to the hash.

      my $pseudo_hash = [ { a => 1, b => 2, c => 3 }, 'A', 'B', 'C' ]; print $pseudo_hash->{b}; # prints 'B' print $pseudo_hash->[2]; # prints 'B' print $pseudo_hash->[$pseudo_hash->[0]->{b}]; # prints 'B'

    Update: at jdporter's suggestion above, I installed and took a look at IxHash. Interestingly, it's implemented as a synthesis of a pseudo-hash and an associative array. The first element of the array is the hash for key/index mappings, and the two subsequent entries in the array are arrays for the keys and data themselves.

Re: Hashes made to order
by gjb (Vicar) on Dec 24, 2002 at 03:39 UTC
      It appears that Tie::IxHash doesn't come with the default installation, and that's one of my requirements: no dependancies on non-default modules.

Re: Hashes made to order
by pg (Canon) on Dec 24, 2002 at 06:26 UTC
    I come up this structure for your reference. I name it OrderedHash. The code given below is strictly for the purpose to explain the structure more clearly, not any kind of actual implementation. This OrderedHash structure contains two parts:
    1. An array, normal array, which contains the key pair values of the OrderedHash. In the Array, those key-value pairs are stored in this sequence: k1, v1, k2, v3, k3, v3...
    2. A hash, normal hash, its keys are the keys of the OrderedHash, values are indexes point to the key of the actual key value pairs in the above array.
    A brief analysis of this structure:
    1. It reserves the order.
    2. It is independent, yet not much implementing effort
    3. We don't invent or implement any hashing algorithm, we simply use a normal hash for indexing.
    4. Its performance is the same as a normal hash. We don't sacrifice performance.
    5. This solution DO sacrifice space to gain performance and to reduce implementation effort, it uses as twice as many space used by a normal hash that has the same number of elements.
    The following code is only to demo the structure.
    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: Hashes made to order
by jryan (Vicar) on Dec 24, 2002 at 06:30 UTC

    If you are dead-set against Tie::IxHash, then you can always try this bit:

    use strict; use warnings; use Want; sub create_ordered() { my (@keys,@values,%order); my $x=0; return sub : lvalue { if (want('LVALUE')) { if (!exists $order{$_[0]}) { $order{$_[0]} = $x++; push @keys, $_[0]; } $values[$order{$_[0]}] = want('ASSIGN'); lnoreturn; } elsif (want('RVALUE')) { rreturn @keys if wantarray; rreturn $values[$order{$_[0]}]; } return; } } # sample use: my $hash = create_ordered; $hash->("first") = "one"; $hash->("second") = "two"; $hash->("third") = "three"; $hash->("fourth") = "four"; $hash->("second") = "tutu"; my @keys = $hash->(); foreach (@keys) { my $x = $hash->($_); print "$x\n"; }

    However, this is *very* cumbersome to use. For instance, you can't delete keys and you can't hash-splice. You also need to be very careful about context. Is there any reason you can't bundle Tie::IxHash with your application? Or at least copy the source code, put it at the top of your script with an author credit?

Re: Hashes made to order
by Aristotle (Chancellor) on Dec 24, 2002 at 17:20 UTC
    Depending on your current interface layout, it may be a nobrainer to add, or it may turn out quite complex. If the former is the case, I'm not sure why this is such a problem - can you post some code? If the latter is, then I'd concur with chromatic.

    Makeshifts last the longest.

      It's for Exporter::VA.

      Basically, when processing the import parameter list, it resolves everything but doesn't actually do it yet. Instead, it keeps a list of name=>ref pairs on a "worklist". Then, a callback is made that may alter this worklist, and finally the worklist is processed.

      So, I have one place where things are added to the list, one place that traverses the list, and maybe places that look things up by name just for warning checking. It might get more later, such as the ability to remove items.

      I'm leaning with chromatic for now, too. As long as I don't box myself into a corner, it should be easy to add later (as another named method) if needed.

      —John

        If this is to be for a CPAN module, then what is wrong with making another CPAN module one of the dependancies?