How can I see if a Perl hash already has a certain key?

Go To StackoverFlow.com

49

I have a Perl script that is counting the number of occurrences of various strings in a text file. I want to be able to check if a certain string is not yet a key in the hash. Is there a better way of doing this altogether?

Here is what I am doing:

foreach $line (@lines){
    if(($line =~ m|my regex|) )
    {
        $string = $1;
        if ($string is not a key in %strings) # "strings" is an associative array
        {
            $strings{$string} = 1;
        }
        else
        {
            $n = ($strings{$string});
            $strings{$string} = $n +1;
        }
    }
}
2009-06-16 19:59
by NoName
The question is, why are you even bothering with that? If it doesn't exist then $n will be undef. Undef's numeric value is 0, so $n+1=1. There's no need to check if it exists in the hash to begin with - Nathan Fellman 2009-06-17 08:00


106

I believe to check if a key exists in a hash you just do

if (exists $strings{$string}) {
    ...
} else {
    ...
}
2009-06-16 20:05
by cpjolicoeur
Be aware that perl will autovivicate any intermediary keys that do not exist in a multidimensional hash in order to "check" if the key your looking for in the last hash exists. It's not a problem with a simple hash like this example but .. my %test = (); print "bar" if(exists $test{'foo'}{'bar'});

perl just autovivified the foo key in order to look for bar

print "foo exists now and you might not have expected that!" if(exists $test{'foo'}) - Drew 2015-09-15 13:52



11

I would counsel against using if ($hash{$key}) since it will not do what you expect if the key exists but its value is zero or empty.

2009-06-16 21:29
by RET
Those certain circumstances are only for nested keys. For this problem, exists is the answer. Don't use exists for nested keys in one shot - brian d foy 2009-06-16 22:20
Downvote is still a bit harsh though - the warning is not invalidated by the simplicity of the script in this question. The more important point is the issue of using if($hash{$key}) with neither defined nor exists: the "zero but true" problem - RET 2009-06-16 23:52
The "zero but true" thing deserves an upvote. But what you said about autovivification is simply wrong and deserves a downvote - innaM 2009-06-17 07:59
The warning here is true in a way - the autovivification might happen, though not with the given example - but the proposed answer with defined() has exactly the same problem, so this is no solution at all - ijw 2009-06-17 12:18
Indeed - fair comment. It was too early in the morning when I wrote that answer, so I've rewritten it now I'm sufficiently caffeinated - RET 2009-06-18 06:59
Upvoted now. It is a fair warning now the autovivification bit has been removed - Leonardo Herrera 2009-06-22 16:23


9

Well, your whole code can be limited to:

foreach $line (@lines){
        $strings{$1}++ if $line =~ m|my regex|;
}

If the value is not there, ++ operator will assume it to be 0 (and then increment to 1). If it is already there - it will simply be incremented.

2009-06-16 21:01
by NoName
While your answer is true, it does answer the question about hashes - Chris 2018-02-15 16:05


6

I guess that this code should answer your question:

use strict;
use warnings;

my @keys = qw/one two three two/;
my %hash;
for my $key (@keys)
{
    $hash{$key}++;
}

for my $key (keys %hash)
{
   print "$key: ", $hash{$key}, "\n";
}

Output:

three: 1
one: 1
two: 2

The iteration can be simplified to:

$hash{$_}++ for (@keys);

(See $_ in perlvar.) And you can even write something like this:

$hash{$_}++ or print "Found new value: $_.\n" for (@keys);

Which reports each key the first time it’s found.

2009-06-16 20:05
by zoul
Yeah, the thing is I won't know ahead of time what the keys will be - NoName 2009-06-16 20:12
Yes, you don't need to check for presence of the key for this purpose. You can simply say $strings{$1}++ . If the key is not there, it will be added with undef as value, which ++ will interpret as 0 for you - Arkadiy 2009-06-16 20:29
Sure. The point is that you can replace the whole body of your cycle (under the if) with $strings{$1}++ - zoul 2009-06-16 20:30
(Sorry, messed up the flow by ‘editing’ my comment : - zoul 2009-06-16 20:31


-1

You can just go with:

if(!$strings{$string}) ....
2009-06-16 20:06
by AJ.
This only works if all of the keys have values that are not false. In general, that's a bad assumption. Use exists(), which is especially designed just for this - brian d foy 2009-06-16 22:21
@brian de foy - Ah ha. I knew I shouldn't have answered :- - AJ. 2009-06-17 01:28
Furthermore, your construct creates an entry in the hash. For the question at hand this is probably irrelevant, but for other cases it might be relevant. Using exists() also circumvents this problem and does not create an entry in the hash - user55400 2009-06-17 09:54
@blixor: No, it doesn't. Try perl -le 'print "ok" if !$a{hello}; print keys %a - Hynek -Pichi- Vychodil 2009-06-17 22:25
Only in nested hashes do you have a problem that intermediate accesses create entries. So $a{$x}{$y} will create $a{$x}, regardless if you use exists or any other approach - rustyx 2016-05-23 14:57
Ads