http://www.perlmonks.org?node_id=1018739

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

Hello Monks )) There is a piece of code:
$Hash{44.52}{param1}{key1} = [101.01,'val1']; $Hash{95.01}{param2}{key2} = [101.02,'val2'];
I want to check if $Hash{80.0}{param1}{key1} exists and contains a value so that 80.0 DOES NOT appar in keys %Hash. So I do:
my $neededVal = 80.0; if (defined $Hash{$neededVal}{param1}{key1}) { print "DEFINED"; } else { for (keys %Hash) { print "$_\n"; #this prints 80.0 as a KEY (among the others)!!! How?.. } }
So does anyone know how to check if a hash value by a particular key exists WITHOUT appearing of this key in (keys %Hash) ? Thanks in advance UPD Thanks everyone! :) When I state no autovivification at start - all ok!

Replies are listed 'Best First'.
Re: Checking if hash value exists without implicitly defining it
by tobyink (Canon) on Feb 14, 2013 at 15:27 UTC

    The feature where

    exists $hash{a}{b}{c}

    ... will cause $hash{a}{b} to spring into existence is called "autovivification" and is a built-in Perl feature; it's often useful, but not always. (And it's a really annoying word to type!) There's a module on CPAN called autovivification that gives you finer control over autovivification.

    package Cow { use Moo; has name => (is => 'lazy', default => sub { 'Mooington' }) } say Cow->new->name
Re: Checking if hash value exists without implicitly defining it
by Ratazong (Monsignor) on Feb 14, 2013 at 15:52 UTC

    Hi Doctrin,

    as others have pointed out, you are suffering from autovivification. In order to avoid autovivification in your specific case, you could also check each level of the hash for existence before dereferencing it (and therefore before autovivification is triggered). The code could look like this:

    if (exists $Hash{$neededVal} && exists $Hash{$neededVal}{param1} && ex +ists $Hash{$neededVal}{param1}{key1}) { print "DEFINED\n"; }
    It takes advantage of the fact that the && s inside the condition are evaluated from left to right, and the evaluation is aborted once a "false" occurs.

    HTH, Rata

Re: Checking if hash value exists without implicitly defining it
by Kenosis (Priest) on Feb 14, 2013 at 16:31 UTC

    One option is to short circuit the autovivification:

    use strict; use warnings; my %Hash; $Hash{44.52}{param1}{key1} = [ 101.01, 'val1' ]; $Hash{95.01}{param2}{key2} = [ 101.02, 'val2' ]; my $neededVal = 80.0; if ( exists $Hash{$neededVal} and exists $Hash{$neededVal}{param1} and exists $Hash{$neededVal}{param1}{key1} ) { print "DEFINED"; } else { for ( keys %Hash ) { print "$_\n"; } }

    Output:

    95.01 44.52

    Update I: Added the needed defined, in case of an earlier (for example) $Hash{$neededVal}{param1}{key1} = 0.

    Update II: Replaced defined with exists. Thank you, choroba.

      Huh. I thought you needed to use exists to avoid AVing the hash key you're reading, but that code works.

      This should simplify some of my code.

        My apologies, as it should be written as:

        ... if ( exists $Hash{$neededVal} and exists $Hash{$neededVal}{param1} and exists $Hash{$neededVal}{param1}{key1} ) { ...

        Will correct the original...

        Update: Replaced defined with exists. Thank you, choroba.

        Well, it only breaks for the false values like 0 or "" or undef.
        لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ

      You don't even need the exists (assuming you don't have a weird freeform data structure):

      if (Hash{$neededVal} and $Hash{$neededVal}{param1} and $Hash{$neededVal}{param1}{key1} > 4) {

        Yes, you're correct! Originally omitted exists when not testing for the final value--then overgeneralized. However, testing $Hash{$neededVal}{param1}{key1} would fail when $Hash{$neededVal}{param1}{key1} == 0, but expliciting testing the value, as you've shown, would remedy that.

Re: Checking if hash value exists without implicitly defining it
by trizen (Hermit) on Feb 14, 2013 at 20:18 UTC
    Here is a recursive check, without autovivification.
    sub _exists { my ($hash, @keys, @exists) = @_; if (exists $hash->{$keys[0]}) { my $key = shift @keys; push @exists, 1, _exists($hash->{$key}, @keys); } return @exists; } sub exists_keys { return &_exists == $#_ ? 1 : (); } my %hash; #$hash{a}{b}{c} = undef; if (exists_keys(\%hash, qw(a b c))) { print "Exists.\n"; } else { print "Doesn't exists.\n"; }
Re: Checking if hash value exists without implicitly defining it
by Anonymous Monk on Feb 14, 2013 at 15:26 UTC