问题
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";
回答1:
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.
回答2:
Thxs for the good stuff!!!
I did it the recursive way:
sub Hash2Array
{
my $this = shift;
my $hash = shift;
my @array;
foreach my $k(sort keys %$hash)
{
my $v = $hash->{$k};
push @array,
ref $v eq "HASH" ? $this->Hash2Array($v, @_, $k) : [ @_, $k, $v ];
}
return @array;
}
It would be interesting to have a performance comparison between all of these solutions...
回答3:
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;
}
回答4:
Made a better version of axeman's i think. Easier to understand without the -> and the \shift to me at least. 3 lines without a subroutine.
With subroutine
sub to_nested_hash {
my $h=shift;
my($ref,$value)=(\$h,pop);
$ref=\%{$$ref}{$_} foreach(@_);
$$ref=$value;
return $h;
}
my $z={};
to_nested_hash($z,1,2,3,'testing123');
Without subroutine
my $z={};
my $ref=\$z; #scalar reference of a variable which contains a hash reference
$ref=\%{$$ref}{$_} foreach(1,2,3); #keys
$$ref='testing123'; #value
#with %z hash variable just do double backslash to get the scalar reference
#my $ref=\\%z;
Result:
$VAR1 = {
'1' => {
'2' => {
'3' => 'testing123'
}
}
};
来源:https://stackoverflow.com/questions/11505100/perl-how-to-turn-array-into-nested-hash-keys