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

Why does the following 'if' condition not work?
#!/usr/bin/perl for (my $n = 0; $n < 268; $n++) { if ($n == (40||47||76)) { print $n."\n"; } }
It only prints out '40', not '47' and '76'. Why does is the 'OR' not work there?

Do I really have to use a regexp here or is there a way to make this work on a purely numerical/logical level in Perl?


(the code is just some sample code to illustrate my problem, the real code where I came across this issue is more complex)

Replies are listed 'Best First'.
Re: my 'if' condition doesn't work, why?
by toolic (Bishop) on Nov 11, 2014 at 21:37 UTC
Re: my 'if' condition doesn't work, why?
by GrandFather (Saint) on Nov 12, 2014 at 01:04 UTC

    This task is almost what grep is designed for:

    for my $n (0 .. 267) { print "$n\n" if grep {$n == $_} 40, 47, 76; }

    with the slight down side that the compare is evaluated for each item in the list always. Most of the time that won't be an issue, but if it is (for a large list of possibilities) then you could instead:

    my %wanted = map {$_ => 1} 40, 47, 76; for my $n (0 .. 267) { print "$n\n" if exists $wanted{$n}; }
    Perl is the programming world's equivalent of English
      The core List::Util first function is like grep but returns the first element found:
      use List::Util qw(first); for my $n (0 .. 267) { print "$n\n" if first {$n == $_} 40, 47, 76; }
      and so should be faster for very large lists.

        A problem with List::Util::first() as used in the example above is that it actually returns the first element from the list that satisfies the condition. If this value happens to be false, the if-statement modifier will not satisfied, and the dependent statement will not be executed. Neither any nor grep nor a hash lookup have this problem.

        c:\@Work\Perl>perl -wMstrict -le "use List::Util qw(first); ;; for my $n (0 .. 267) { printf qq{$n } if first {$n == $_} 40, 0, 47, 76; } print qq{\n----------}; ;; ;; for my $n (0 .. 267) { printf qq{$n } if grep { $n == $_ } 40, 0, 47, 76; } " 40 47 76 ---------- 0 40 47 76

        On average 50% faster which often doesn't help much. However if speed is an issue and the search needs to be done multiple times with the same data, using a hash lookup is a much better solution.

        Perl is the programming world's equivalent of English
Re: my 'if' condition doesn't work, why?
by philipbailey (Curate) on Nov 11, 2014 at 21:38 UTC

    The expression (40||47||76) "short-circuits" and evaluates to 40. Scalar $n is then only compared to 40.

    Edit: the solution is as toolic suggests, though I tend to use the low-precedence or operator by default (rather than the high precedence one, ||), as it more often does what I expect.

Re: my 'if' condition doesn't work, why?
by AnomalousMonk (Archbishop) on Nov 11, 2014 at 21:39 UTC

    The particular expression  (40||47||76) evaluates only to one value: 40. That's because of the nature of the logical-or operator; see perlop. Maybe try something like:

    c:\@Work\Perl\monks>perl -wMstrict -le "my %hit = map { $_ => 1 } 40, 47, 76; ;; for (my $n = 0; $n < 268; $n++) { if ($hit{$n}) { print $n; } } " 40 47 76

    Update: Or even with a more Perlish for-loop:

    c:\@Work\Perl\monks>perl -wMstrict -le "my %hit = map { $_ => 1 } 40, 47, 76; ;; for my $n (0 .. 267) { if ($hit{$n}) { printf qq{$n }; } } " 40 47 76

Re: my 'if' condition doesn't work, why?
by Anonymous Monk on Nov 11, 2014 at 21:43 UTC

    Yes, you can do it just fine with a regex, and see the other monks' answers. And TIMTOWTDI :-)

    use Quantum::Superpositions 'any'; # OR #use Perl6::Junction 'any'; for (my $n = 0; $n < 268; $n++) { if ($n == any(40,47,76)) { print $n."\n"; } }
      This looks like the 'nicest' (from a code readability and compactness point of view) solution to me, shame that it's not standard Perl but instead requires an extra module.

      So I have chosen to use
      if ($n==40 || $n==47 || $n==76) {
      which works fine too (regardless whether I use 'or' or '||').

      Many thanks to all who replied for your help.
        shame that it's not standard Perl but instead requires an extra module

        Well, you can get a similar result using List::Util, which is a core module:

        #! perl use strict; use warnings; use List::Util 'any'; for my $n (0 .. 267) { print "$n\n" if any { $n == $_ } (40, 47, 76); }

        Update: Fixed off-by-one error in the for loop range, thanks to AnomalousMonk.

        Output:

        17:34 >perl 1074_SoPW.pl 40 47 76 17:34 >

        Hope that helps,

        Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

        "shame that it's not standard Perl but instead requires an extra module"

        Well, there does exist something similar in core Perl since 5.10: the smart match operator! Example:

        if ($n ~~ [40, 47, 76]) { ...; }

        However, smart match acts pretty weird in some cases (not the above case), so it is not usually a great idea to use it.