Affecting a value to a multilevel hash in one inst

2019-08-11 11:09发布

I am looking for a more effective way to write this affect subroutine in :

#!/usr/bin/perl
use 5.010;
use strict;

my @keys = qw/foo bar baz/;
my %hash = ( foo => {babel => 'fish'} );

affect (\%hash, 42);

sub affect {
   my $ref = shift;
   $ref = $ref->{$keys[$_]} //= {} for (0.. $#keys - 1);
   $ref->{$keys[$#keys]} = shift;
}

use Data::Dumper;
print Dumper \%hash;

And the expected result:

$VAR1 = {
          'foo' => {
                     'bar' => {
                                'baz' => 42
                              },
                     'babel' => 'fish'
                   }
        };

I was thinking about things like this, but obviously it can't work like this:

%hash{ @keys } = 42;

Any idea?

标签: perl hash
1条回答
祖国的老花朵
2楼-- · 2019-08-11 11:13

it would be more effective if you actually passed the keys to it! Let's make it an lvalue sub at the same time.

sub dive_val :lvalue {
   my $p = \shift;
   $p = \( $$p->{$_} ) for @_;
   $$p
}

my %hash;
dive_val(\%hash, qw( foo babel )) = 'fish';
dive_val(\%hash, @keys) = 42;

There are two added advantages to this implementation.

First, you can use it to peek into the data structure.

print(dive_val($hash, @keys), "\n");

Second, it will autovivify its first argument.

my $hash;
dive_val($hash, @keys) = 42;

This function already exists as Data::Diver's DiveVal.

use Dive::Val qw( DiveVal );

DiveVal(\%hash, map \$_, @keys) = 42;

Comparison of the flow of affect and dive_val:

  • affect

    $ref is a reference to a hash.

    Pre-loop:            $ref references %hash
    After loop pass 0:   $ref references %{ $hash{$key[0]} }
    After loop pass 1:   $ref references %{ $hash{$key[0]}{$key[1]} }
    Post loop uses:      $ref->{$key[2]}
    
  • dive_val

    $p is a reference to a scalar.

    Pre-loop:            $p references $hash
    After loop pass 0:   $p references $hash->{$key[0]}
    After loop pass 1:   $p references $hash->{$key[0]}{$key[1]}
    After loop pass 2:   $p references $hash->{$key[0]}{$key[1]}{$key[2]}
    Post loop uses:      $$p
    

dive_val's approach is purer.

  • There's no need to create the hash before it's dereferenced (unlike affect which creates it in the previous loop pass).
  • In fact, the scalar doesn't need to be in a hash at all. This means dive_val could easily be extended to support mixed array/hash structures.
  • There's no need to treat the root specially (if you want to accept a potentially undefined argument).
  • There's no need to treat the last key specially.
查看更多
登录 后发表回答