开发者

Perl, check if pair exists in hash of hashes

In Perl, I have a hash of hashes created with a loop similar to the following

    my %HoH
    for my $i (1..10) {
      $HoH{$a}{$b} = $i; 
    }

$a and $b are variables that do have some value when the HoH gets filled in. After creating t开发者_JS百科he HoH, how can I check if a particular pair ($c, $d) exists in the HoH? The following does not work

if (defined $HoH{$c}{$d}) {...}

because if $c does not exist in HoH already, it will be created as a key without a value.


Writing

if (defined $HoH{$c}{$d}) {...}

will "work" insomuch as it will tell you whether or not $HoH{$c}{$d} has a defined value. The problem is that if $HoH{$c} doesn't already exist it will be created (with an appropriate value) so that $HoH{$c}{$d} can be tested. This process is called "autovivification." It's convenient when setting values, e.g.

my %hoh;
$hoh{a}{b} = 1; # Don't need to set '$hoh{a} = {}' first

but inconvenient when retrieving possibly non-existent values. I wish that Perl was smart enough to only perform autovivification for expressions used as lvalues and short-circuit to return undef for rvalues but, alas, it's not that magical. The autovivification pragma (available on CPAN) adds the functionality to do this.

To avoid autovivification you need to test the intermediate values first:

if (exists $HoH{$c} && defined $HoH{$c}{$d}) {
     ...
}


use Data::Dumper;

my %HoH;

$HoH{A}{B} = 1;

if(exists $HoH{C} && exists $HoH{C}{D}) {
   print "exists\n";
}

print Dumper(\%HoH);

if(exists $HoH{C}{D}) {
   print "exists\n";
}

print Dumper(\%HoH);

Output:

$VAR1 = {
          'A' => {
                   'B' => 1
                 }
        };
$VAR1 = {
          'A' => {
                   'B' => 1
                 },
          'C' => {}
        };

Autovivification is causing the keys to be created. "exists" in my second example shows this so the first example checks both keys individually.


Several ways:

if ( $HoH{$c} && defined $HoH{$c}{$d} ) {...}

or

if ( defined ${ $HoH{$c} || {} }{$d} ) {...}

or

no autovivification;
if (defined $HoH{$c}{$d}) {...}

or

use Data::Diver;
if ( defined Data::Diver::Dive( \%HoH, $c, $d ) ) {...}


You have to use the exists function

exists EXPR

Given an expression that specifies an element of a hash, returns true if the specified element in the hash has ever been initialized, even if the corresponding value is undefined.

Note that the EXPR can be arbitrarily complicated as long as the final operation is a hash or array key lookup or subroutine name:

  1. if (exists $ref->{A}->{B}->{$key}) { }
  2. if (exists $hash{A}{B}{$key}) { }


My take:

use List::Util   qw<first>;
use Params::Util qw<_HASH>;

sub exists_deep (\[%$]@) {
    my $ref = shift;
    return unless my $h = _HASH( $ref ) // _HASH( $$ref )
              and defined( my $last_key = pop )
              ;
    # Note that this *must* be a hash ref, for anything else to make sense.
    return if first { !( $h = _HASH( $h->{ $_ } )) } @_;
    return exists $h->{ $last_key };
}

You could also do this recursively. You could also create a descent structure allowing intermediate and even terminal arrayref with just a little additional coding.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜