http://qs321.pair.com?node_id=747216


in reply to Building an arbitrary-depth, multi-level hash

It's a little tricky, but not hard once you've seen it done once:

my %hash; my $value = "foo"; my @cats = qw(a b c d); my $p = \%hash; foreach my $item (@cats) { $p->{$item} ||= {}; $p = $p->{$item}; } $p->{_val} = $value; use Data::Dumper; print Dumper(\%hash);

Output:

$VAR1 = { 'a' => { 'b' => { 'c' => { 'd' => { '_val' => 'foo' } } } } };

The trick here is to keep a pointer ($p) to the insertion-point for the next category as you walk through the structure. You also need some way to distinguish values from categories - in this case I used a special "_val" key.

-sam

Replies are listed 'Best First'.
Re^2: Building an arbitrary-depth, multi-level hash
by ikegami (Patriarch) on Feb 28, 2009 at 22:15 UTC
      Wow, no. This is basic Perl and shouldn't require a module! I love modules, probably much more than the next guy, but there's a point at which it becomes silly. What's next Scalar::Increment? Loop::For?

      UPDATE: Just looked at the code for Data::Diver. Double wow!

      -sam

        Basic?

        It's a FAQ.
        People continually get it wrong.
        People have no idea how it works when given that solution.
        It's missing half the code.
        It's 6 lines instead of one. (7 for the fetcher if you don't validate.)

        I have no problem writing such code, but I'm not most people. Put it into an appropriately named function and I'll be able to read it too. Or you know, use the function that's already written.

Re^2: Building an arbitrary-depth, multi-level hash
by Anonymous Monk on Feb 28, 2009 at 22:57 UTC
    my $p = \%hash; ... $p = $p->{$item};
    AH! Thank you, sir - you're a scholar and a gentleman. *THAT* is the thing that I'd been struggling toward. A thousand thanks, and I'm going to save this thing (I don't think I'm going to go as far as tattooing it on my forehead, but you never know...)
Re^2: Building an arbitrary-depth, multi-level hash
by repellent (Priest) on Mar 01, 2009 at 21:13 UTC

      does not leave around refs to empty hashes (it leaves undef's instead).

      What does that mean? In fact, vivification is required here.

      use strict; use warnings; use Data::Dumper qw( Dumper ); sub samtregar { my $p = shift; my $val = pop; for my $item (@_) { $p->{$item} ||= {}; $p = $p->{$item}; } $p->{_val} = $val; } sub repellent { my $p = \shift; my $val = pop; $p = \$$p->{$_} for @_; $$p->{_val} = $val; } { samtregar(my $samtregar={}, qw( a b c d ), 'foo'); repellent(my $repellent, qw( a b c d ), 'foo'); { local $Data::Dumper::Useqq = 1; local $Data::Dumper::Terse = 1; local $Data::Dumper::Indent = 0; print("samtregar: ", Dumper($samtregar), "\n"); print("repellent: ", Dumper($repellent), "\n"); } }
      samtregar: {"a" => {"b" => {"c" => {"d" => {"_val" => "foo"}}}}} repellent: {"a" => {"b" => {"c" => {"d" => {"_val" => "foo"}}}}}

      And for a getter, what you said is backwards. Yours is the one that vivifies.

      use strict; use warnings; use Data::Dumper qw( Dumper ); sub samtregar_fetcher { my $p = shift; for my $item (@_) { last if !$p->{$item}; $p = $p->{$item}; } return $p->{_val} } sub repellent_fetcher { my $p = \shift; my $val = pop; $p = \$$p->{$_} for @_; return $$p->{_val}; } { samtregar_fetcher(my $samtregar={}, qw( a b c d )); repellent_fetcher(my $repellent, qw( a b c d )); { local $Data::Dumper::Useqq = 1; local $Data::Dumper::Terse = 1; local $Data::Dumper::Indent = 0; print("samtregar: ", Dumper($samtregar), "\n"); print("repellent: ", Dumper($repellent), "\n"); } }
      samtregar: {} repellent: {"a" => {"b" => {"c" => {}}}}
        Sorry, I was too brief. What I meant was in the absence of assigning value to the key _val: (note the change with comments)
        use strict; use warnings; use Data::Dumper qw( Dumper ); sub samtregar { my $p = shift; my $val = pop; for my $item (@_) { $p->{$item} ||= {}; $p = $p->{$item}; } ### $p->{_val} = $val; } sub repellent { my $p = \shift; my $val = pop; $p = \$$p->{$_} for @_; ### $$p->{_val} = $val; } { samtregar(my $samtregar={}, qw( a b c d ), 'foo'); repellent(my $repellent, qw( a b c d ), 'foo'); { local $Data::Dumper::Useqq = 1; local $Data::Dumper::Terse = 1; local $Data::Dumper::Indent = 0; print("samtregar: ", Dumper($samtregar), "\n"); print("repellent: ", Dumper($repellent), "\n"); } } __END__ samtregar: {"a" => {"b" => {"c" => {"d" => {}}}}} repellent: {"a" => {"b" => {"c" => {"d" => undef}}}}

        while the nested hashes are being built, it doesn't leave an empty {} around. This behavior could be desirable.

        I didn't mean to say that mine doesn't vivify. It's almost the same, just subtly different as shown above.

        Hmmm ... as for the fetchers, shouldn't it be more consistent with:
        use strict; use warnings; use Data::Dumper qw( Dumper ); sub samtregar_fetcher { my $p = shift; for my $item (@_) { ### last if !$p->{$item}; $p->{$item} ||= {}; $p = $p->{$item}; } return $p->{_val} } sub repellent_fetcher { my $p = \shift; ### my $val = pop; $p = \$$p->{$_} for @_; return $$p->{_val}; } { samtregar_fetcher(my $samtregar={}, qw( a b c d )); repellent_fetcher(my $repellent, qw( a b c d )); { local $Data::Dumper::Useqq = 1; local $Data::Dumper::Terse = 1; local $Data::Dumper::Indent = 0; print("samtregar: ", Dumper($samtregar), "\n"); print("repellent: ", Dumper($repellent), "\n"); } } __END__ samtregar: {"a" => {"b" => {"c" => {"d" => {}}}}} repellent: {"a" => {"b" => {"c" => {"d" => {}}}}}

Re^2: Building an arbitrary-depth, multi-level hash
by zentara (Archbishop) on Mar 01, 2009 at 15:30 UTC