Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?
 
PerlMonks  

How to test for empty hash?

by scareduck (Novice)
on Aug 05, 2021 at 19:47 UTC ( #11135626=perlquestion: print w/replies, xml ) Need Help??

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

I have been using Perl since the 4.x days, so I am having the devil's own time trying to figure out how to detect an empty hash in 5.32.1.

It seems to me that

scalar(%hash)
should return 0 if the hash is empty. Similarly, another approach would be !%hash, but this appears to return false. Similarly,
my @a = keys(%hash); if ($#a == -1) ...
doesn't work, because $#a returns 0! What am I missing/doing wrong?

Replies are listed 'Best First'.
Re: How to test for empty hash?
by haukex (Bishop) on Aug 05, 2021 at 21:12 UTC

    The node Truth and Falsehood also briefly discusses this. In short, a hash in boolean context returns a false value when it is empty, so if (!%hash) will portably detect an empty hash. To portably get the number of keys in a hash, use keys in scalar context; scalar(%hash) is not backwards compatible for counting keys (as the documentation quoted by LanX explains).

    Minor edits clarification.

      This is a bit confusingly written.

      The question was about checking if a hash is empty, and %h in scalar context can be used to do this portably (though the relative recent change makes it more efficient).


      That said,

      %h in scalar context can't be used to get the number of keys portably.

      keys(%h) in scalar context can be used to get the number of keys portably and efficiently.

      That's not true. You could aways use There was a relatively recent change thatefficiency of hash in boolean context

      Seeking work! You can reach me at ikegami@adaelis.com

        This is a bit confusingly written. That's not true. You could aways use There was a relatively recent change thatefficiency of hash in boolean context
      update: it's wrong, only read further to learn from mistakes ;-)


      > to portably get the number of keys in a hash, use keys in scalar context; scalar(%hash) is not backwards compatible

      nitpick for the sake of fun, b/c of the magic of the string_to_number conversion using it as a number will portably work.

      (tho I'd also prefer using keys here :)

      DB<1> p $] 5.024001 DB<2> %h=(a=>0) DB<3> p scalar %h 1/8 DB<4> p 0+ %h 1 DB<5>

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery

        nitpick for the sake of fun, b/c of the magic of the string_to_number conversion using it as a number will portably work.

        Sorry, no, it doesn't:

        $ perlbrew exec perl -e '%h="a".."z";warn"$] ".keys%h," ".%h," ",0+%h, +"\n"' >/dev/null 5.034000 13 13 13 5.032001 13 13 13 5.030003 13 13 13 5.028003 13 13 13 5.026003 13 13 13 5.024004 13 8/16 8 5.022004 13 10/16 10 5.020003 13 7/16 7 5.018004 13 11/16 11 5.016003 13 9/16 9 5.014004 13 9/16 9 5.012005 13 9/16 9 5.010001 13 9/16 9 5.010000 13 9/16 9 5.008009 13 9/16 9 5.008001 13 12/16 12 5.006002 13 4/8 4

        Minor edits to shorten output.

Re: How to test for empty hash?
by jdporter (Chancellor) on Aug 05, 2021 at 19:55 UTC

    You're on the right track, but the way to test for the array (of keys) being empty is:

    if ( @a == 0 )

    You can skip the intermediary variable:

    if ( keys(%hash) == 0 )
Re: How to test for empty hash?
by eyepopslikeamosquito (Bishop) on Aug 06, 2021 at 00:29 UTC
Re: How to test for empty hash?
by haj (Priest) on Aug 05, 2021 at 19:55 UTC
    This works for me:
    my %hash = (); # empty hash print "empty!" unless (%hash);
    However, this does print -1:
    my @a = keys(%hash) print "$#a\n";
    So maybe your hash isn't actually empty?
      > This works for me:

      And it's well documented defined in perldata (tho hard to find)

      The exact value is only version dependent if the hash is not empty, but still guaranteed to be true (unless tied to magic behaviour)

      Maybe not relevant for the OP but IMHO still of interest to people used to the old behaviour.

        If you evaluate a hash in scalar context, it returns a false value if the hash is empty. If there are any key/value pairs, it returns a true value. A more precise definition is version dependent.

        Prior to Perl 5.25 the value returned was a string consisting of the number of used buckets and the number of allocated buckets, separated by a slash. This is pretty much useful only to find out whether Perl's internal hashing algorithm is performing poorly on your data set. For example, you stick 10,000 things in a hash, but evaluating %HASH in scalar context reveals "1/16", which means only one out of sixteen buckets has been touched, and presumably contains all 10,000 of your items. This isn't supposed to happen.

        As of Perl 5.25 the return was changed to be the count of keys in the hash. If you need access to the old behavior you can use Hash::Util::bucket_ratio() instead.

        If a tied hash is evaluated in scalar context, the SCALAR method is called (with a fallback to FIRSTKEY).

      > So maybe your hash isn't actually empty?

      Or tied?

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery

      Tip: Hashes are created empty, so = () is useless noise.

      Seeking work! You can reach me at ikegami@adaelis.com

Re: How to test for empty hash?
by GrandFather (Saint) on Aug 06, 2021 at 01:18 UTC

    Interesting. I have this thing about not doing 0 == blah because I think that reads better as !blah so I wouldn't have had the problem. In many C++ish languages the two are essentially the same so it's pretty much personal preference between an ugly verbose numeric compare and a clean succinct boolean test. In Perl, as you may have found, it can be more subtle than that!

    That said, how were you performing your empty test? The simple version works fine for me:

    use strict; use warnings; my %hash; print "$^V\n"; print "Empty: !\%hash\n" if !%hash; print "Empty: ! scalar\n" if ! scalar %hash; print "Empty: 0 == scalar\n" if 0 == scalar %hash; print "Empty: ! keys\n" if ! keys %hash; ++$hash{entry}; print "Not empty: \%hash\n" if %hash; print "Not empty: scalar\n" if scalar %hash; print "Not empty: 0 != scalar\n" if 0 != scalar %hash; print "Not empty: keys\n" if keys %hash;

    Prints:

    v5.32.1 Empty: !%hash Empty: ! scalar Empty: 0 == scalar Empty: ! keys Not empty: %hash Not empty: scalar Not empty: 0 != scalar Not empty: keys

    Update: fixed mismatch between test and print for 0 == scalar

    Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
      Ah hah! This example clarifies my thinking a great deal. As I started this exercise writing a regression test, I kept having trouble inside ok() and is(). (This was using the Test2 suite, but here I will just use Test::Simple.)
      #!/usr/bin/perl use Test::Simple (tests => 9); use strict; use warnings; my %hash; &ok($^V eq "v5.32.1","Version = v5.32.1"); &ok(!%hash,"Empty: !\%hash"); &ok(!scalar(%hash), "Empty: ! scalar"); &ok(scalar(%hash) == 0, "Empty: 0 == scalar"); &ok(!keys(%hash), "Empty: ! keys"); ++$hash{entry}; &ok((%hash ? 1 : 0),"Not empty: \%hash"); &ok(scalar(%hash), "Not empty: scalar"); &ok(scalar(%hash) != 0, "Not empty: scalar != 0"); &ok((keys(%hash) ? 1 : 0),"Not empty: keys");
      This works in its entirety:
      1..9 ok 1 - Version = v5.32.1 ok 2 - Empty: !%hash ok 3 - Empty: ! scalar ok 4 - Empty: 0 == scalar ok 5 - Empty: ! keys ok 6 - Not empty: %hash ok 7 - Not empty: scalar ok 8 - Not empty: scalar != 0 ok 9 - Not empty: keys
      Thanks, all!

        As a matter of style, I would write your test something like this:

        use strict; use warnings; use Test::More; my $ntests = 9; plan tests => $ntests; { my $expected_version = 'v5.32.1'; cmp_ok( $^V, 'eq', $expected_version, "Version = $expected_version" + ); } { my %hash; ok( !%hash, "Empty: !\%hash" ); ok( !scalar(%hash), "Empty: ! scalar" ); cmp_ok( scalar(%hash), '==', 0, "Empty: 0 == scalar" ); ok( !keys(%hash), "Empty: ! keys" ); ++$hash{entry}; ok( (%hash ? 1 : 0), "Not empty: \%hash" ); ok( scalar(%hash), "Not empty: scalar" ); cmp_ok( scalar(%hash), '!=', 0, "Not empty: scalar != 0" ); ok( (keys(%hash) ? 1 : 0), "Not empty: keys" ); }

        Some points to note:

        • I always prefer Test::More to Test::Simple because that scales better as test scripts grow more complex over time.
        • At the top of your test script, you should explicitly declare how many tests your script intends to run, to protect against premature failure. I always use plan for this because as test scripts grow more complex over time you sometimes need to calculate this number.
        • I pulled a face the instant I saw your &ok() function calling style! Haven't seen that dreadful old style for 20 years! :) From Perl Best Practices: Call subroutines with parentheses but without a leading & (item 113). Update: see also.
        • Minimize the scope of variables. I find bare blocks a convenient way to do this in test scripts. Just because it is a test script doesn't mean you should drop basic standards of code quality.
        • Prefer cmp_ok to ok because you get clearer diagnostics when a test fails (see below for an example).
        • Don't repeat yourself. Test scripts are programs and you should follow the same coding quality standard in the test scripts as for the code under test. Doing this makes long term code maintenance more enjoyable and less error-prone.

        When I first ran your test script, it failed with:

        not ok 1 - Version = v5.32.1 # Failed test 'Version = v5.32.1' # at badtests1.pl line 8.
        Note that using cmp_ok instead of ok provides clearer diagnostics:
        # Failed test 'Version = v5.32.1' # at badtests2.pl line 9. # got: 'v5.32.0' # expected: 'v5.32.1'

        See Also

Re: How to test for empty hash?
by perlfan (Vicar) on Aug 08, 2021 at 02:28 UTC
    I came here to suggest scalar, as in,
    perl -e 'my %x=(); print scalar %x' 0

    but then, this caused me pause:

    perl -e 'my %x=undef; print scalar %x' 1/8
        With all this talk of scalar, I had to look it up because, despite using Perl heavily for twenty years, I've never actually used it! Which reminded me that I have employed the equivalent "secret" version, namely the infamous inchworm ~~ secret operator

        Just a little nitpick: the "inchworm" isn't exactly equivalent, as it doesn't pass through lvalue context, which scalar does as of 5.22.

        $ perlbrew exec perl -e 'for(scalar($#foo)) { $_=3 } warn "$] ".@foo." +\n"' >/dev/null 5.034000 4 5.032001 4 5.030003 4 5.028003 4 5.026003 4 5.024004 4 5.022004 4 5.020003 0 5.018004 0 $ perlbrew exec perl -e 'for(~~$#foo) { $_=3 } warn "$] ".@foo."\n"' > +/dev/null 5.034000 0 5.032001 0 5.030003 0 5.028003 0 5.026003 0 5.024004 0 5.022004 0 5.020003 0 5.018004 0

        Update: The example above is one of the exceptions for when ~~ is not equivalent to scalar anyway:

        $ perl -le 'print scalar($#foo)' -1 $ perl -le 'print ~~$#foo' 18446744073709551615

        ... but the point still stands (though interestingly, my $foo="x"; for(scalar($foo)) {$_.="y"} worked even before 5.22).

      No cause for pause. undef is promoted to the empty string, used as a hash key and assigned undef as its value because that's the default value of items in the empty list | used by Perl when a key in a hash assignment list has no corresponding value, or Odd number of elements in hash assignment.

      Win8 Strawberry 5.8.9.5 (32) Sat 08/07/2021 23:15:45 C:\@Work\Perl\monks >perl use strict; use warnings; use Data::Dump qw(dd); my %h = undef; print scalar %h, "\n"; dd \%h; ^Z Odd number of elements in hash assignment at - line 7. Use of uninitialized value in list assignment at - line 7. 1/8 { "" => undef }
      The hash ends up with 1 key/value pair.

      Update: Clarified (I hope!) the explanation of odd-item hash assignment.


      Give a man a fish:  <%-{-{-{-<

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11135626]
Approved by LanX
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others meditating upon the Monastery: (2)
As of 2022-06-27 05:25 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    My most frequent journeys are powered by:









    Results (86 votes). Check out past polls.

    Notices?