http://www.perlmonks.org?node_id=516797

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

Hi,
  I was writting a long list of OR's (||) in several IF statements, then it occured to me. I'm sure there is way of listing all the or values rather than repeating the same variable and each individual value. After searching on the net, I haven't come up with anything, so I've come for some sage wisdom from the best place possible.

IF ($variable == 1 || $variable == 2 || $variable == 3) { ... IF ($vaiables == [1,2,3?????????


Lyle

Update: Great to see so many responses and expecially great to see that Perl6 will have an a'any' function to deal with this nicely. In my opinion for overall speed and efficiency bageler's solution is best:-
sub any { my $var = shift; for (@_) { return 1 if $var == $_ } } if (any($var,1..2000)) { print "Var is between 1 and 2000 inclusive\n" + }

Replies are listed 'Best First'.
Re: Using IF and OR, I'm sure there is a better way
by ikegami (Patriarch) on Dec 14, 2005 at 23:37 UTC
    And for something completely different:
    # Setup my @ok; ++$ok[$_] for 1, 2, 3; # Sample usage if ($ok[$variable]) { ... }
    For more flexibility, try a hash:
    # Setup my %fruit; ++$fruit{$_} for qw( apple banana orange ); # Sample usage if ($fruit{$object}) { ... }
    These are just one step away from a dispatch table:
    # Setup my %dispatch = ( colour => \&area_fill, scale => \&scale_image, warp => \&warp_image, ); # Sample usage if ($handler = $dispatch{$command}) { $handler->(); } else { die("Invalid command $command\n"); }

    Update: Keep in mind that 1, 2, 3 can be written as 1..3

Re: Using IF and OR, I'm sure there is a better way
by cog (Parson) on Dec 14, 2005 at 23:33 UTC
    Or perhaps:

    use List::MoreUtils; if (any { $variable == $_ } (1, 2, 3)) { ... }

    With the disadvantage of using an extra module, but with the advantage of not testing every single value after the first successful one (if the list is big, it's probably a better alternative).

Re: Using IF and OR, I'm sure there is a better way
by cog (Parson) on Dec 14, 2005 at 23:29 UTC
    For instance:

    if (grep { $variable == $_ } (1, 2, 3)) { ... }
      This unfortunately is significantly longer to execute than the boolean OR. The grep has to test every value, whereas the if statement uses short-circuit boolean logic to stop testing values as soon as a match is found.
        "significantly"? Maybe a significant factor longer, but if the OP was even considering writing it in a long || ... || ... ||, there aren't enough elements to make it significant in absolute terms.
Re: Using IF and OR, I'm sure there is a better way
by GrandFather (Saint) on Dec 14, 2005 at 23:33 UTC

    This may do it for you:

    use strict; use warnings; my $variable = 3; if ($variable =~ /\b(?:1|2|3)\b/) { print "case 1, 2 or 3\n" }

    Prints:

    case 1, 2 or 3

    DWIM is Perl's answer to Gödel
Re: Using IF and OR, I'm sure there is a better way
by eff_i_g (Curate) on Dec 15, 2005 at 00:22 UTC
    I use something like this:
    sub in_list { my $needle = shift || q{}; # or croak, your choice. croak 'expecting haystack' if not @_; return grep m/^$needle$/, @_; } if (in_list($var, qw(A B C))) { ... }
      or return grep $_ eq $needle, @_; rather.
Re: Using IF and OR, I'm sure there is a better way
by Fletch (Bishop) on Dec 15, 2005 at 01:23 UTC

    You might also consider populating a hash and using exists to check for a match.

    use YAML (); $items = YAML::Load( <<EOT ); a: 1 b: 1 c: 1 EOT if( exists $items->{ $variable } ) { # . . . }

      Is that really how you populate your hashes?

      $items = { a => 1, b => 2, c => 3, }; if ( exists $items->{ $variable } ) { # . . . }

          -Bryan

        Depends. If the list were relatively static I probably would keep it inline in the source like you've shown. If it were more dynamic or dependent on something outside the script I'd leave it external with a quick change from YAML::Load to YAML::LoadFile (granted I'd probably use a YAML sequence rather than a hash, but the idea is the same). It also could move to the bottom of the file after an __END__ marker and remain with the script but be more segregated from the flow of code.

Re: Using IF and OR, I'm sure there is a better way
by davido (Cardinal) on Dec 15, 2005 at 03:00 UTC

    List::MoreUtils' any() sub is one good way that someone mentioned. Another is a hashtable. But if the values are all contiguous, and you don't mind that floating point values could fall between '1' and '2', you could do it this way too:

    if( $variable >= 1 and $variable <= 3 ) { #...

    Dave

Re: Using IF and OR, I'm sure there is a better way
by phaylon (Curate) on Dec 15, 2005 at 12:55 UTC
    I'd like to add 'any' from Perl6::Junction:
    if ( any( 1, 2, 3 ) eq $n ) { ...

    Ordinary morality is for ordinary people. -- Aleister Crowley

      Hey, it looks like the Perl6 people have been reading up on Fortran 90...(although it's quite possible any was adapted to Fortran90 from some other language).

      Fortran 90's any statement returns true of false if any element in an array meets a test condition, e.g.,

      integer,dimension(6) :: ar ar = (/1,3,4,5,6,7/) if(any(ar > 4) then # returns true if any element of ar is greater tha +n 4 call a_miracle_happens_here endif

      I'm still wondering why everybody was using regex for the test; it looks like a numerical comparison to me.

Re: Using IF and OR, I'm sure there is a better way
by swampyankee (Parson) on Dec 15, 2005 at 03:47 UTC

    If your condition is going to be predictably in a numerical range like that, this may work.

    if($variable >= $lower_limit && $variable <= $upper_limit) { miraculous_stuff; }

    If your requirement is to do something when $variable is an integer, vs something else when it isn't, or when $variable is being tested against values in an array, this probably would not be appropriate, but if it's a regular as what I read at 10:46pm EST 14 Dec 2005, my suggestion may be appropriate.

Re: Using IF and OR, I'm sure there is a better way
by l.frankline (Hermit) on Dec 15, 2005 at 04:09 UTC

    Here is an another possible way...

    For single numeric values:

         print $& if ($variable=~/[123]/);

    For two or more numeric values:

         print $& if ($variable=~/[1][0]+/);

    Also, take a look at perlrequick

    Regards
    Franklin

    Don't put off till tomorrow, what you can do today.

      these will work even if it is a double digit in $var (ie: 1 and 10 will both succeed for the first example) but if you only want one digit in range maybe something like:
      print $& if $var =~ /^[1-3]$/;
      or ... better yet ... if you are going to be doing this alot and with strings as well as digits ...
      sub look_into { my $archetype= shift; eval 'sub { $_[0] =~ /$archetype/o; }' } my $is_text_am_too_red = look_into q/\b(am|too|red)\b/; my $single_digit_in_range = look_into q/^[1-5]$/; my $double_digit_in_set = look_into q/^[5-7][49]$/; # etc. if($is_text_am_too_red ->($_)){ print "get some sleep!" } if($single_digit_in_range ->($_)){ print "single digit in range!" } if($double_digit_in_set ->($_)){ print "double digit in set!" } #etc.

      Using $& can slow down regexps elsewhere in the program, so replace
      print $& if ($variable=~/[123]/);
      with
      print $1 if ($variable=~/([123])/);

Re: Using IF and OR, I'm sure there is a better way
by bageler (Hermit) on Dec 15, 2005 at 17:51 UTC
    write your own shortcircuiting any() if you don't want to use cpan:
    sub any { my $var = shift; for (@_) { return 1 if $var == $_ } } if (any($var,1..2000)) { print "Var is between 1 and 2000 inclusive\n" + }
      Think I like this one best. I'm sure it'll bench fastest as well. Firing up the regexp engine to compare will have a noticeable overhead.
Re: Using IF and OR, I'm sure there is a better way
by robin (Chaplain) on Dec 16, 2005 at 01:40 UTC
    In Perl 5.10, you'll be able to do:
    use feature '~~'; if ($variable ~~ [1,2,3]) { ... }
    which will indeed short-circuit.

    (I'm backporting the Perl 6 "smart match" operator to Perl 5.)

Re: Using IF and OR, I'm sure there is a better way
by bravenmd (Sexton) on Dec 15, 2005 at 20:30 UTC
    try this:

    if($variable =~ /1|2|3/){
    ...
    }
      I'd recommend trying this vs seperate elsif statements on a large file and seeing if it makes a difference.

      6 weeks ago I was optimising a process reading in a daily network syslog file of approx 1200 MB using 5.8.0 on an overworked Ultra 2 running Solaris 2.6.

      The script had been using next if (/abc|cde|fgh|/) which *looks* most concise, but I found (to my and my workmate's surprise) that replacing it with the equivalent elsif statements made it take about 30 seconds less (it had been taking >2 minutes to read the file in).

Re: Using IF and OR, I'm sure there is a better way
by ahmad (Hermit) on Dec 15, 2005 at 20:50 UTC

    it depends on what you're going to do after knowing if it's equal to or not , This may help

    my @array = qw/1 2 3 4/; my $matched; foreach (@array){ if($variable == $_){ $matched = $_;} }

    it's not the best way but it may be helpful i think

Re: Using IF and OR, I'm sure there is a better way
by mathy (Initiate) on Dec 16, 2005 at 16:20 UTC
    Hey Lyle,
    I figured out a way using a regular expressions comparison:

    $variable=1; if ($variable =~/[1,2,3]/) { print "Did it!\n"; }
    I'm new in Perl, so this may be completely non-sense, but it works :)
    Tell me something about this, ok?
    Mathy