Stories
Slash Boxes
Comments
NOTE: use Perl; is on undef hiatus. You can read content, but you can't post it. More info will be forthcoming forthcomingly.

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

Wednesday April 28, 2004
03:18 PM

Multi-level hashes without eval or modules

[ #18530 ]

Last night a friend passed along a short problem to me. I need to take the keys for a multi-level hash, say $hash{a}{b}{c}{d}, and create that structure from just the list qw(a b c d), without using eval or a module, and it had to be really short, and it had to be delivered right away

After my initial solution, I came up with this concept (the argument checking and so on to be done by someone else). I use two hash references. One references the top level of the hash I want to create, and the other one is something like a cursor inside that hash so I can move around in it.

sub set_multi_level_hash
    {
    my( $hash, $tiers, $value ) = @_;
 
    my $work_hash = $hash;
    foreach my $tier ( @$tiers )
            {
            $work_hash->{ $tier } = {} unless exists $work_hash->{ $tier };
            $work_hash = $work_hash->{ $tier };
            }
 
    $work_hash = _get_multi_hash_last_level( $hash, $tiers );
 
    $work_hash->{ $tiers->[-1] } = $value;
    }

I do not really like that part where I have to back up a bit to figure out where to assign the value. I am sure there is a better way to do this, but I got the problem at 1 am and I needed to deliver it right away.

The _get_multi_hash_last_level routine uses the same trick as the earlier routine, but I do not have to remember anything about the original hash. I just need to drill-down to the right level.

sub _get_multi_hash_last_level
    {
    my( $hash, $tiers ) = @_;
 
    foreach my $tier ( @{$tiers}[0..$#$tiers-1] )
        {
        $hash = $hash->{ $tier };
        }
 
    $hash;
    }

That may look short, but it was longer before because I had made a mistake that required me to handle two special cases. The original version worked, but before I was able to become unconscious in bed, I came up with that version.

Somewhere else, somebody else created the code to read the multi-level hash under the same restrictions, I think, but I did not get to see that code. I wonder what it does to get to the right place.

The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
 Full
 Abbreviated
 Hidden
More | Login | Reply
Loading... please wait.
  • The only real innovation is using only one hash, and using the tier indices to avoid the backing up.

    use strict;
    use warnings;

    use Data::Dumper;

    my $hash = {};

    set_multi_level_hash($hash, [qw/ a b c d /], 'potato');

    print Dumper $hash;

    sub set_multi_level_hash
          {
          my ( $hash, $tiers, $value ) = @_;

          for (0 .. $#$tiers)
                {
                $hash->{$tiers->[$_]} ||= ( $_ == $#$t
    • Oops, to be able to change the value, I suppose the first line in the for loop should be split into:

      $hash->{$tiers->[$_]} = {} unless exists $hash->{$tiers->[$_]};
      $hash->{$tiers->[$_]} = $value if $_ == $#$tiers;
    • Yeah, I thought about something like that, but then I should go through 0..$#$tiers-1, and do the special case for the last value.

      Oh well, too late now. It is out of my hands.

      Thanks for the code :)
      • I think one needs to specify what happens in this case:

        set_multi_level_hash($h, [qw(a b c)], 42);
        set_multi_level_hash($h, [qw(a b)], 66);

        Oops, we just nuked the first value and the whole "subhash".

        In other words, is the level of multiness going to stay the same?

        Personally, I would either create a class that guards against the multiness changing, or would make the data structure to be an array reference of hash references, keyed off in the array reference on the depth of $tiers (the latter can of course
        • What you are "supposed" to get depends on what your requirements are. In this case, you assign a different value to a hash key, you replace the value, just like you do if you had written it out as "$h->{a}{b} = 42".

          I suppose you could create a class to limit operations, but then, you probably would have need ed a lot more time to deliver the code. One of the requirements was "right now". I didn't build in a lot of extra stuff to make variables not be variable.

          There is no get_multi_level_hash functio
  • I had to do someting similar to create a multilevel hash table that returns a list of "cached" things given a list of parameters that represent the hashing value.

    The main problem was not creating the various anonymous hashes, but that the last item is not a {} but a []. The trick was to keep a reference to the previous step, to be able to go back and turn a newly created hash reference into an anonymous array.

    # $thing is a hash reference
    # @keys is the list of $thing attributes I'm using for the hashing a