in reply to Recursively walk a hash to get to an element

The following is the same solution but faster since it eliminates all those slow sub calls:

sub DiveRef { my \$p = \shift; \$p = \( \$\$p->{\$_} ) for @_; \$p } my %myhash; my \$ref = DiveRef(\%myhash, qw( bedrock flintstone fred )); -or-- my \$myhash; my \$ref = DiveRef(\$myhash, qw( bedrock flintstone fred )); \$\$ref = 42; # set it to 42 \$\$ref++; # increment it print \$\$ref; # print it!

Replies are listed 'Best First'.
Re^2: Recursively walk a hash to get to an element
by LanX (Cardinal) on Apr 04, 2021 at 19:20 UTC
Adding some syntactic sugar with prototype and lvalue (not sure when the + prototype was introduced UPDATE: 5.14.0 code corrected)

Checking the ref-type would help diving arrays too. (but would probably reinvent Data::Diver )

use v5.14; use warnings; use Test::More; sub dive(+;@) :lvalue { my \$h = \ shift; \$h = \(\$\$h->{\$_}) for @_; \$\$h; } my %hsh; my \$h_ref = \%hsh; my \$val = \$hsh{a}{b}{c} = 3; my \$val2 = 42; my @path = qw/a b c/; my \$test; ;;;;;;;;;; \$test = "Prototypes"; is( dive(%hsh => @path) , \$val, "\$test accepts \%list form"); is( dive(\$h_ref=> @path) , \$val, "\$test accepts \\$ref form"); ;;;;;;;;;; \$test = "assign directly to lvalue"; dive(%hsh=> @path) = \$val2; is( \$hsh{a}{b}{c}, \$val2, \$test); ;;;;;;;;;; \$test = "increment in place"; dive( %hsh=> @path )++; is( \$hsh{a}{b}{c}, ++\$val2, \$test); ;;;;;;;;;; \$test = "grab scalar reference"; my \$s_ref = \ dive(%hsh, @path); \$\$s_ref++; is( \$hsh{a}{b}{c}, ++\$val2, \$test); ;;;;;;;;;; \$test = "aliasing"; for my \$alias ( dive(%hsh, @path) ) { \$alias++; } is( \$hsh{a}{b}{c}, ++\$val2, \$test); done_testing;
OUTPUT:
ok 1 - Prototypes accepts %list form ok 2 - Prototypes accepts \$ref form ok 3 - assign directly to lvalue ok 4 - increment in place ok 5 - grab scalar reference ok 6 - aliasing 1..6

UPDATE: cosmetic corrections

Cheers Rolf
(addicted to the Perl Programming Language :)
Wikisyntax for the Monastery

The code I posted was based on code that did use :lvalue, but I adapted it to match the OP.

I strongly recommend using an existing solution such as Data::Diver.

use Data::Diver qw( DiveVal ); my @keys = map "level\$_", 1 .. 3; my \$branch = {}; DiveVal(\$branch, map \\$_, @keys) = 'leaf'; -or- my %branch; DiveVal(\%branch, map \\$_, @keys) = 'leaf';

Obviously, it can be done without module too.

sub DiveVal :lvalue { my \$p = \shift; \$p = \( \$\$p->{\$_} ) for @_; \$\$p } my @keys = map "level\$_", 1 .. 3; my \$branch; DiveVal(\$branch, @keys) = 'leaf'; -or- my %branch; DiveVal(\%branch, @keys) = 'leaf';

How my DiveVal works:

Pre-loop: \$p references \$branch After loop pass 0: \$p references \$branch->{level1} After loop pass 1: \$p references \$branch->{level1}{level2} After loop pass 2: \$p references \$branch->{level1}{level2}{level3} Returned: \$branch->{level1}{level2}{level3}

The extra level of indirection has many benefits.

• It removes the need to treat the last key specially.
• It removes the need to create the hash before it's dereferenced.
• It removes the need for the root to be a reference to a hash. Instead, any scalar can be the root, even an undefined one.
• It makes it easy to extend DiveVal to support mixed array/hash structures.

Being fundamentally the same solution, the explanation applies to Merlyn's code too. Just substitute \$a for \$p, and it's a reference to the final scalar that's returned instead of the scalar itself as an lvalue.