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

Hello monks!
I have 4 hashes and I want to check which keys from a 5th hash are NOT present at any of the 4 hashes. I tried:
foreach $a(keys %test_hash) { if(not exists($hash1{$a}) && not exists($hash2{$a}) && not exists( +$hash3{$a}) && not exists($hash4{$a})) { print $test_hash{$a}; } }

but it didn;t work, since I got a list of keys that were supposedly only present in the 5th hash (test_hash), but when I checked 1-2 by hand, I found out that they were also present in one or more of the other hashes as well.
What m I doing wrong here?
Thanks!

Replies are listed 'Best First'.
Re: Check if a key exists in several hashes at once
by kennethk (Abbot) on Nov 12, 2010 at 22:22 UTC
    You are having a problem with operator precedence. && is a higher precedence operator than not. For example, print not 0 && not 1 outputs 1 because you are really executing print not(0 && not(1)). You will get your expected result by either using the high-precedence ! or low-precedence and. See Operator Precedence and Associativity for more info.

      Note that this common precedence booboo can be avoided by following the advice in Chapter 4, "Values and Expressions" of Perl Best Practices.

      See especially item 4.16: "Don't mix high- and low-precedence booleans".

Re: Check if a key exists in several hashes at once
by ikegami (Patriarch) on Nov 12, 2010 at 23:21 UTC
    Another solution:
    ++$any{$_} for keys(%hash1), keys(%hash2), keys(%hash3), keys(%hash4); for (keys(%test_hash)) { print("$test_hash{$_}\n") if !$any{$_}; }
Re: Check if a key exists in several hashes at once
by Tanktalus (Canon) on Nov 13, 2010 at 04:16 UTC

    My preference is for code to read exactly like the English that describes it. Your spec says "not ... any", so the closest I can come to that is:

    use List::MoreUtils qw(any); foreach my $k (keys %test_hash) { if (not any { exists $_->{$k} } \%hash1, \%hash2, \%hash3, \%hash4) { print $k; } }
    Of course, "not any" is grammatically the same as "none", i.e., saying "not present in any" is exactly the same, both in meaning and intent, as "present in none", so you could swap it out:
    use List::MoreUtils qw(none); foreach my $k (keys %test_hash) { if (none { exists $_->{$k} } \%hash1, \%hash2, \%hash3, \%hash4) { print $k; } }
    They should take exactly the same amount of time because they should short circuit exactly the same.

    I don't actually suggest this over Anonymous Monk's solution for speed reasons. I suggest this because the code reads linguistically the same as your spec. It just happens that it performs as well or better than the grep solution.

    The grep solution actually returns a list in boolean context. My solution returns boolean in boolean context. In my mind, that is a win right there because the code says the same thing as the spec. There are fewer mental logic changes where someone may have to stop and think about how it works. Reading just like the spec means that the maintenance programmer will just be able to read the code without even necessarily being familiar with List::MoreUtils. In fact, the only reason they would check the docs for List::MoreUtils would be to see what other cool things it had.

Re: Check if a key exists in several hashes at once
by Anonymous Monk on Nov 12, 2010 at 23:22 UTC
    unless ( grep { exists $$_{$foo} } \%hash1, \%hash2, \%hash3 ) {

    (You could also use (core module) List::Util's first in the exact same way, but it's not going to make a noticeable difference unless you've benchmarked that this is slowing you down.)

Re: Check if a key exists in several hashes at once
by TomDLux (Vicar) on Nov 13, 2010 at 02:55 UTC

    You get the key from %test_hash, and check if it exists as a key in the other hashes, but if it doesn't, you print out %test_hash value associated with the key. Maybe yoou were just trying to simplify your code for us, but it does suggest you're confusing keys and values.

    Besides the mixed-precedence bolean operations, I mean ... I've been finding perlcritic useful in isolating tons of minor weaknesses in some ghastly code I'm bringing up to modern standards, I'll probably be using it more often in my routine development. Mixed-precedence booleans is just one of many things it warns about ... Be sure you have it on the harshest setting.

    As Occam said: Entia non sunt multiplicanda praeter necessitatem.