Search code examples
perlperl-data-structures

Perl: How to turn array into nested hash keys


I need to convert a flat list of keys into a nested hash, as follow:

my $hash = {};

my @array = qw(key1 key2 lastKey Value);

ToNestedHash($hash, @array);

Would do this:

$hash{'key1'}{'key2'}{'lastKey'} = "Value";


Solution

  • sub to_nested_hash {
        my $ref   = \shift;  
        my $h     = $$ref;
        my $value = pop;
        $ref      = \$$ref->{ $_ } foreach @_;
        $$ref     = $value;
        return $h;
    }
    

    Explanation:

    • Take the first value as a hashref
    • Take the last value as the value to be assigned
    • The rest are keys.
    • Then create a SCALAR reference to the base hash.
    • Repeatedly:
      • Dereference the pointer to get the hash (first time) or autovivify the pointer as a hash
      • Get the hash slot for the key
      • And assign the scalar reference to the hash slot.
      • ( Next time around this will autovivify to the indicated hash ).
    • Finally, with the reference to the innermost slot, assign the value.

    We know:

    • That the occupants of a hash or array can only be a scalar or reference.
    • That a reference is a scalar of sorts. (my $h = {}; my $a = [];).
    • So, \$h->{ $key } is a reference to a scalar slot on the heap, perhaps autovivified.
    • That a "level" of a nested hash can be autovivified to a hash reference if we address it as so.

    It might be more explicit to do this:

    foreach my $key ( @_ ) { 
        my $lvl = $$ref = {};
        $ref    = \$lvl->{ $key };
    }
    

    But owing to repeated use of these reference idioms, I wrote that line totally as it was and tested it before posting, without error.

    As for alternatives, the following version is "easier" (to think up)

    sub to_nested_hash {
        $_[0] //= {};
        my $h     = shift;
        my $value = pop;
        eval '$h'.(join '', map "->{\$_[$i]}", 0..$#_).' = $value';
        return $h;
    }
    

    But about 6-7 times slower.