Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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";

like image 981
Christophe Avatar asked Dec 12 '22 00:12

Christophe


2 Answers

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.

like image 148
Axeman Avatar answered Dec 14 '22 13:12

Axeman


I reckon this code is better - more amenable to moving into a class method, and optionally setting a value, depending on the supplied parameters. Otherwise the selected answer is neat.

#!/usr/bin/env perl

use strict;
use warnings;
use YAML;

my $hash = {};

my @array = qw(key1 key2 lastKey);
my $val = [qw/some arbitrary data/];

print Dump to_nested_hash($hash, \@array, $val);
print Dump to_nested_hash($hash, \@array);
sub to_nested_hash {
    my ($hash, $array, $val) = @_;
    my $ref   = \$hash;
    my @path = @$array;
    print "ref: $ref\n";
    my $h     = $$ref;
    $ref      = \$$ref->{ $_ } foreach @path;
    $$ref     = $val if $val;
    return $h;
}
like image 34
singingfish Avatar answered Dec 14 '22 15:12

singingfish