janssene has asked for the wisdom of the Perl Monks concerning the following question:

I need to fill in a multi_dimensional-hash. currently I use :(just an example) $cf{$k1}{$k2}{$k3}{"hlst"} ="" if (! defined $cf{$k1}{$k2}{$k3}{"hlst"}); Is there a way to put this in a subroutine and use the hash_ref and some 'list of keys' to perform this operation to make it independent of the nbr of keys ?

Replies are listed 'Best First'.
Re: define a hash-value, using hash-ref and a list of keys
by roboticus (Chancellor) on Oct 29, 2014 at 13:17 UTC

    janssene:

    Yes, that's possible. it should be something like:

    use strict; use warnings; use Data::Dump qw(pp); my @a = qw(now is the time for all good men); my %h; add_val(\%h, 'FOO', @a); print pp(\%h); sub add_val { my ($hashref, $val, @keylist) = @_; die "Expected a list of keys!" unless @keylist; die "Expected a hashref!" unless "HASH" eq ref $hashref; while (@keylist > 1) { my $key = shift @keylist; $hashref->{$key} = {} if ! exists $hashref->{$key}; $hashref = $hashref->{$key}; } $hashref->{shift @keylist} = $val; }

    Untested, your mileage may vary, you can keep all the pieces it might break into, join(", ",@other_disclaimers).

    Update: Fixed & tested this time.

    ...roboticus

    When your only tool is a hammer, all problems look like your thumb.

      roboticus++: Interesting, so at each iteration of the while loop it autovivifies the next key and you end up with a reference to the final memory location.

      # As long as we have multiple keys, access the next subhash while (@keys > 1) { my $key = shift @keylist; $hashref = $hashref->{$key}; }

      Should that be @keylist instead of @keys?

      while (@keylist > 1) { my $key = shift @keylist; $hashref = $hashref->{$key}; }

      Update: I just tried this and it doesn't work. $hashref = $hashref->{$key}; doesn't set anything in the hash and doesn't autovivify. I end up with an empty hash.

      use strict; use warnings; use Data::Dump; my %testhash; my $hr = \%testhash; my $count = 0; while(<DATA>) { print "[", join('-',split ' ', $_), "]\n"; add_val( $hr, ++$count, split ' ', $_); } dd \%testhash; sub add_val { my ($hashref, $val, @keylist) = @_; die "Expected a list of keys!" unless @keylist; die "Expected a hashref!" unless "HASH" eq ref $hashref; # As long as we have multiple keys, access the next subhash while (@keylist > 1) { my $key = shift @keylist; $hashref = $hashref->{$key}; } # Now set the value $hashref->{shift @keylist} = $val; } __DATA__ a1 a2 a3 jjj kkk lll mmm Output: [a1-a2-a3] [jjj-kkk-lll-mmm] {}

      Update again: With the addition of the subhash from your update it works. Thanks.

      Output: [a1-a2-a3] [jjj-kkk-lll-mmm] { a1 => { a2 => { a3 => 1 } }, jjj => { kkk => { lll => { mmm => 2 } } }, }

        Lotus1:

        Yes, it should be @keylist. I also needed to create the subhash when it didn't exist. I've made the appropriate corrections to the parent node.

        ...roboticus

        When your only tool is a hammer, all problems look like your thumb.

Re: define a hash-value, using hash-ref and a list of keys
by Loops (Curate) on Oct 29, 2014 at 13:13 UTC
    You could make a subroutine, but you can use //= assignment for the equivalent of the code example you gave:
    $cf{$k1}{$k2}{$k3}{"hlst"} //= "";
    But that assumes the problem you're trying to solve is duplicating that variable twice.
      > $cf{$k1}{$k2}{$k3}{"hlst"} //= "";

      IMHO the best solution in this case where autovivification is wanted.

      For completeness if the perl version is to old for defined-or, one could use a ref to the hash value.

      $ perl use Data::Dumper; my $hash; my $rval = \ $hash{a}{b}{c}{"hlst"}; $$rval = "" unless defined $$rval; print Dumper \%hash; __END__ $VAR1 = { 'a' => { 'b' => { 'c' => { 'hlst' => '' } } } };

      or you can define an defined-or-assign function to do so

      use Data::Dumper; my $hash; defor( $hash{a}{b}{c}{"hlst"} , ""); print Dumper \%hash; sub defor { $_[0] = $_[1] unless defined $_[0]; } __END__ $VAR1 = { 'a' => { 'b' => { 'c' => { 'hlst' => '' } } } };

      Cheers Rolf

      (addicted to the Perl Programming Language and ☆☆☆☆ :)

Re: define a hash-value, using hash-ref and a list of keys
by Laurent_R (Canon) on Oct 29, 2014 at 19:16 UTC
    One possible solution only briefly tested under the debugger:
    DB<1> @keylist = qw/one two three four/; DB<2> $hash = (); DB<3> sub make_hash {$string = join '}{', @_ ; $string = '$hash{' +. $string . '} = 0'; eval $string;} DB<4> make_hash(@keylist); DB<5> x \%hash; 0 HASH(0x6004f9dc0) 'one' => HASH(0x6004f9d48) 'two' => HASH(0x6004f9c10) 'three' => HASH(0x6004f9be0) 'four' => 0 DB<6> make_hash( qw/jan feb mar apr may jun/) DB<7> x \%hash; 0 HASH(0x6004f9dc0) 'jan' => HASH(0x60064b430) 'feb' => HASH(0x60064b508) 'mar' => HASH(0x60064b538) 'apr' => HASH(0x600500860) 'may' => HASH(0x60064eab0) 'jun' => 0 'one' => HASH(0x6004f9f58) 'two' => HASH(0x600639458) 'three' => HASH(0x6006175e8) 'four' => 0
    I did not try further, but it appears to work. One thing missing is the check of the eval return value, but this was just a kind of quick proof of concept. Passing additional arguments such as an hash ref and the value to be assigned, rather than hardcoding them, seems to be an implementation details.
Re: define a hash-value, using hash-ref and a list of keys (diver)
by tye (Sage) on Oct 29, 2014 at 14:30 UTC
Re: define a hash-value, using hash-ref and a list of keys
by janssene (Initiate) on Oct 29, 2014 at 13:56 UTC
    thx for the swift response... much appreciated. learned about some "//=" operator, never seen , but I'm a newbie !! learned the way to construct a hash_ref.... again many thanks...
      Hi Roboticus, just wondering, my problem is that I can have situations where some keys can be not defined yet. so when filling in my value..$cf{$k1}{$k2}{$k3} = "$val", that $k2 and $k3 are not defined yet in the hash, for the given $k1. is there some specific command to create the ref for $cf{$k1}{$k2} before going further on $k3 ?
        Perl does this automatically, it's called autovivification.

        You only have an issue if you want to avoid it.

        update

        hmm thats the problem if you post from a mobile client without possibility to test...

        I'll update a working solution, though using Data::Diver should help already.

        update

        use warnings; use strict; use Data::Dumper; my @keys="a".."d"; my %hash=(); my $href=\%hash; my $lastkey= pop @keys; for my $key (@keys) { $href = $href->{$key} //= {} ; } $href->{$lastkey} = ""; print Dumper \%hash;
        --->
        $ perl dive.pl $VAR1 = { 'a' => { 'b' => { 'c' => { 'd' => '' } } } };

        update
        fixed bug

        Cheers Rolf

        (addicted to the Perl Programming Language and ☆☆☆☆ :)

        janssene:

        Yes, I've botched that in my original answer. I've fixed it, though. (I also notice that LanX's repair is cleaner than mine. I need to start using the //= operator more often.

        ...roboticus

        When your only tool is a hammer, all problems look like your thumb.