4

I need to find the max value of all values in a Perl hash. I don't need the key, just the highest value, so that I can increase it and return a new value that is higher than before. Simplest thing in the world. I took inspiration from this answer: https://stackoverflow.com/a/2891180/2740187, and implemented this piece of code:

use List::Util qw( reduce min max );
my %gid = ("abc" => 1, "def" => 1);
my $gid_ref = \%gid;
my $max_gid = reduce { $a > $b ? $a : $b } values %$gid_ref || 0;
print "$max_gid\n"; 

As you can see, the hash only contains two 1's as values. Then why is it printing "2"? At least it does on my machine.

I guess I just could write

my $max2 = max(values %$gid_ref) || 0;

to get the max value anyway, but I really would like to understand what is happening here.

Community
  • 1
  • 1
jackthehipster
  • 978
  • 8
  • 26
  • 3
    "I guess I just could write `my $max2 = max ...`" That's called "self-documenting code." `max` is understandable at a glance, while `reduce` requires an extra second to parse. When you have multiple ways to solve the same problem, you should typically go with the most readable approach. – ThisSuitIsBlackNot Jan 22 '15 at 18:12
  • Totally agree, and I will do that in my code. I just stumbled upon that solution and used it a while ago. But one never stops learning... – jackthehipster Jan 23 '15 at 07:42

2 Answers2

8

This line

my $max_gid = reduce { $a > $b ? $a : $b } values %$gid_ref || 0;

is parsed as

my $max_gid = reduce { $a > $b ? $a : $b } (values %$gid_ref || 0);

and

(values %$gid_ref || 0)

is using values in scalar context, which evaluates to the number of elements in the hash (2). Since 2 || 0 is still 2, you perform your reduction on a scalar and get back the value.

On the other hand

my $max_gid = (reduce { $a > $b ? $a : $b } values %$gid_ref) || 0;

does get the maximum of the sequence as intended.

What's the purpose of the || 0 anyway? The documentation recommends

If your algorithm requires that reduce produce an identity value, then make sure that you always pass that identity value as the first argument to prevent undef being returned.

So you probably want

my $max_gid = reduce { $a > $b ? $a : $b } 0, values %$gid_ref;
ikegami
  • 367,544
  • 15
  • 269
  • 518
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • 2
    I think you meant `(0, values %$gid_ref)` for that last bit. – cjm Jan 22 '15 at 17:56
  • @cjm: Thanks for spotting that. Indeed `0` was supposed to be before the `values` – Ben Voigt Jan 22 '15 at 17:58
  • 1
    Might want to note that `values $gid_ref` will only work for Perl 5.14+ and that the [feature](http://perldoc.perl.org/functions/values.html) is "considered highly experimental." – ThisSuitIsBlackNot Jan 22 '15 at 18:08
  • @ThisSuitIsBlackNot: No, that was just a typo leaving out the `%`. – Ben Voigt Jan 22 '15 at 18:13
  • @ikegami: The result of `values` isn't a list? Or `values` itself detects the scalar context and doesn't bother building the list (surprising, because if one *wants* the number of elements in a hash, there'd be no reason to use `values`) – Ben Voigt Jan 22 '15 at 18:43
  • 3
    @Ben Voigt, Not in scalar context, it's not. It's impossible to return a list in scalar context. It's up to each operator to decide what they return in scalar context. (In fact, a list in scalar context returns the last element of the list, not the number of elements in the list. e.g. `perl -E'say scalar( 5,6,7 )'`) – ikegami Jan 22 '15 at 18:49
  • 1
    and given that, the number of elements is the most sensible thing to return – ysth Jan 22 '15 at 18:51
  • Holy cow! That's exactly why I don't really like Perl ;-) It's a terrific language with terrific pitfalls all over the place. And thanks for the tip on using reduce correctly. – jackthehipster Jan 23 '15 at 07:41
2

You can also use lower precedence or operator instead of ||, which doesn't impose scalar context on values()

my $max_gid = (reduce { $a > $b ? $a : $b } values %$gid_ref or 0);

(parentheses are mandatory here and warnings will complain if you omit them)

mpapec
  • 50,217
  • 8
  • 67
  • 127
  • I don't really see how an operator with the wrong precedence is better than another operator with the wrong precedence. I guess because of the warnings? – Ben Voigt Jan 23 '15 at 15:18
  • @BenVoigt I can't tell I'm following your line of thought. What have you in mind? – mpapec Jan 23 '15 at 15:23