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

Hi all, I'm not much of a programmer really, my question is very simple I hope it doesn't irritate you too much. I've got to write a bunch of statements with copious 'or' alternatives which is going to look like this:
if ($a == 'ABA' || $a == 'SCO' || $a == 'ACC'|| $a == 'PHC'|| $a == 'G +HF') {something}
How do I spare those endless repetitions of $a == 'something' || $a == 'some other thing'? In Oracle SQL for example you could write
where a = in (ABA, SCO, ACC, PHC, GHF)
(SQL as a benchmark is lame, I know) Is there anything like this in Perl?.. I probably could use arrays but I'm not sure

Replies are listed 'Best First'.
Re: Sparing multiple 'or's
by Tux (Canon) on Jun 04, 2018 at 15:25 UTC

    Personally, I'd use a hash:

    my %expr = ( EX_CASE1 => { map { $_ => 1 } qw( ABA SCO ACC PHC GHF ) }, EX_CASE2 => {}, # … ); : : if (exists $expr{EX_CASE1}{$a}) { ... } : if (exists $expr{EX_CASE2}{$b}) { ... }

    but I'd choose more expressive names than EX_CASE1 etc :)

    You can also use List::Util's any, but that might read fine, but is very hard to optimize, so it will probably be slower the your current code

    use List::Util qw( any ); if (any { $a eq $_ } qw( ABA SCO ACC PHC GHF )) { .... }

    Enjoy, Have FUN! H.Merijn

      ++ For the hash method, although your nested structure makes it look more complex than the other solutions. It can be made to look much simpler using a single level, and the fact that there won't be autovivification in boolean context:

      # equivalent to %is_valid = (ABA => 1, SCO => 1, ACC => 1...); my %is_valid = map { $_ => 1 } qw( ABA SCO ACC PHC GHF ); if ($is_valid{$variable}) # Easy to read { ... }

        I coded for expansion. With the OP's case, I do expect their code to be littered with statements like that. What would you choose? On hash, like I did, with explicit names/keys indicating the purpose of the hash entry (easily expandable) or numerous single-level hashes, all maintained differently.

        If it is just for this single statement, it is a fun-to-read thread for all approaches, but none is an actual improvement over the original or'd eqs. I would not change a thing. If this kind of expression will appear all over the code, I'd choose the simplest to maintain method. YMMV.


        Enjoy, Have FUN! H.Merijn
Re: Sparing multiple 'or's
by Eily (Monsignor) on Jun 04, 2018 at 15:24 UTC

    You can either use grep like this: if (grep {$variable eq $_} qw( ABA SCO ACC PHC GHF )) (where qw builds a list of words).
    Or use List::Util's any (with its more intuitive name, and slightly faster since it will stop searching as soon as a match is found):

    use List::Util qw( any ); if (any {$variable eq $_} qw( ABA SCO ACC PHC GHF ))

    $a is a special variable, than can be used by sort or some functions of List::Util, that's why I used $variable instead.

    And also note that I used eq, which compares two strings, while == compares numbers. Perl will actually complain about this if you have warnings enabled (which you really should, as it lets perl tell you where it thinks you have done a mistake, and it is often right).

Re: Sparing multiple 'or's
by dave_the_m (Monsignor) on Jun 04, 2018 at 15:26 UTC
    I'm presuming you intended string rather than numeric comparisons (so $a eq 'ABA'). In which case this does it:
    if ($a =~ /^ ( ABA | SCO | ACC | PHC | GHF ) $/x ) { ... }

    Dave.

      This is how I would do it, but I generally use a non-capturing group whenever possible for clarity:

      if ( $a =~ /^ (?: ABA | SCO | ACC | PHC | GHF ) $/x ) { ... }

      Note that this is more for capturing intent than performance. When I see a capture group (...), I assume we will be using whatever was captured. In this case, since we are capturing the entire string, $a == $1.

      Probably not worth the nitpick :)

      Best,

      Jim

      πάντων χρημάτων μέτρον έστιν άνθρωπος.

Re: Sparing multiple 'or's
by hippo (Archbishop) on Jun 04, 2018 at 15:25 UTC

    TIMTOWTDI, but here are two methods (grep and regex) to get you started:

    #!/usr/bin/env perl use strict; use warnings; my @cases = qw/ABA SCO ACC PHC GHF/; my $re = join '|', @cases; for my $text ('SCO', 'Microsoft') { print "$text found (grep)\n" if grep {$text eq $_} @cases; print "$text found (regex)\n" if $text =~ /^$re$/; }

    PS. It would be better not to use $a as a variable name since that is special with respect to sorting.

      Note that the results for the examples of the two methods differ:

      c:\@Work\Perl\monks>perl -wMstrict -le "my @cases = qw/ABA SCO ACC PHC GHF/; my $re = join '|', @cases; ;; for my $text ('SCO', 'ENDOSCOPE', 'Microsoft') { print qq{'$text' found (grep)} if grep { $text eq $_ } @cases; print qq{'$text' found (regex)} if $text =~ /^$re$/; } " 'SCO' found (grep) 'SCO' found (regex) 'ENDOSCOPE' found (regex)
      See haukex's Building Regex Alternations Dynamically.


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

        Good catch (++). As dave_the_m suggested we can use brackets in this case:

        #!/usr/bin/env perl use strict; use warnings; use utf8; my @cases = qw/ABA SCO ACC PHC GHF/; my $re = join '|', @cases; for my $text ('SCO', 'ENDOSCOPE', 'Microsoft') { print "$text found (grep)\n" if grep {$text eq $_} @cases; print "$text found (regex)\n" if $text =~ /^($re)$/; }
Re: Sparing multiple 'or's
by LanX (Saint) on Jun 04, 2018 at 15:26 UTC
    grep in scalar context

     grep { $a eq $_ }  'ABA', 'SCO', 'ACC', 'PHC' , 'GHF'

    gives you the number of successful tests.

    I.e. it's true in boolean context as long as it matches at least once.

    HTH! :)

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

      I would prefer to use the function 'any' from List::MoreUtils rather than 'grep' because the name seems to make the intention clearer. It may even be slightly faster because it can quit at the first match.
      Bill

        A bunch of stuff (everything?) from List::MoreUtils was migrated | copied to the core module List::Util some time ago, including any() used by Eily in the example here. (But List::MoreUtils is still around for those on ancient systems :)


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

        Sure , but

        >corelist List::MoreUtils Data for 2013-01-20 List::MoreUtils was not in CORE (or so I think)

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

Re: Sparing multiple 'or's
by bliako (Abbot) on Jun 04, 2018 at 22:05 UTC

    Denis, you have not told us whether your flags are mutually exclusive ($a can contain ABA - only) or they can be turned on at the same time ($a can contain ABA and SCO).

    In the olden days, in the caves, they used *flags*. Each flag was denoted by a digit in a binary word. The binary word making up the "options". Flags as binary words had the added benefit of having a bunch of them turned on at the same time. The negative side is that they are all binary/integers and made little sense to the user without consulting the manual - though most times they were internal and nobody cared about their values, e.g. C's open()'s O_CREAT and O_APPEND flags.

    Here is a refresher (translated from C):

    #!/usr/bin/env perl use strict; use warnings; use Getopt::Long; # our constants use constant ABA => 0b00001; # 0b... for binary number use constant SCO => 0b00010; use constant ACC => 0b00100; my $a = 0b0; Getopt::Long::GetOptions( 'ABA' => sub { $a |= ABA }, 'SCO' => sub { $a |= SCO }, 'ACC' => sub { $a |= ACC }, ) or die "error command line"; if( $a & SCO ){ print "it has SCO!\n"; } if( $a & ABA ){ print "it has ABA!\n"; } if( $a & ACC ){ print "it has ACC!\n"; }

    This is quite fast but I am sure there will be aesthetic objections including mine (which balances with my sentimental counter-objections).

    In the Present, we do away with all these and instead use string constants and string comparisons which are more expensive.

    I will not push any further the point of "cost" and "speed", but I will highlight the point of "multiple flags coexisting in a variable". Which is very useful as it reflects real-life situations. I am sure whizkids can make flags stored in strings turned on at the same time by devising a way to encode them in a string which can be robust yet ... quite unreadable, like the binary flags.

    Against the binary flags is the MAXINT limitation which let's say is 2^64, it will limit the max number of flags to check on to 64 :(

    PS. If this forum were another, I am sure someone would have suggested a class for handling this kind of situations.

    PS2. Flags and Semaphores!

Re: Sparing multiple 'or's
by haukex (Archbishop) on Jun 04, 2018 at 19:54 UTC
Re: Sparing multiple 'or's
by davido (Cardinal) on Jun 05, 2018 at 15:07 UTC

    my @alternatives = qw(ABA SCO ACC PHC GHF); my $target = 'ABA'; if (grep {$target eq $_} @alternatives) { print "$target is in (@alternatives)\n"; } else { print "$target is not in (@alternatives)\n"; }

    The meat is this:  if (grep {$target eq $_} @alternatives) ....

    Here's how it works. First, grep will iterate over the elements in @alternatives, and compare each element to your $target value. In scalar context (provided by the if(CONDITION) construct), grep returns the number of matches. In Perl, any numeric value that is not zero is true in a Boolean sense. So getting a match on one of the elements will make the condition true.

    You can set up an "all" relationship (instead of "any") like this:

    if (@alternatives == grep {$target eq $_} @alternatives) {...

    Here we're asserting that every element in @alternatives must match $target.


    Dave

Re: Sparing multiple 'or's
by Denis (Initiate) on Jun 04, 2018 at 19:35 UTC
    Gentlemen, thank you all for the incredible support you've given! I never thought anyone would bother answering!
Re: Sparing multiple 'or's
by tobyink (Canon) on Jun 06, 2018 at 12:14 UTC
Re: Sparing multiple 'or's
by clueless newbie (Curate) on Jun 04, 2018 at 21:00 UTC

    Smart match?

    if ($a ~~ qw(ABA SCO ACC PHC GHF)) { ... }

      Smart matching is experimental, and may be drastically changed or potentially even removed in future versions of Perl.

Re: Sparing multiple 'or's
by karlgoethebier (Abbot) on Jun 08, 2018 at 07:35 UTC

    Creative code reuse AKA cheating (#5 and #6):

    #!/usr/bin/env perl use strict; use warnings; use English; use feature qw(say); sub any (&@); my $item = 'ABA'; my @items = qw(ABA SCO ACC PHC GHF); if ( any { $ARG eq $item } @items ) { say q(Cool!); } else { say q(Crap!); } sub any (&@) { my $f = shift; foreach (@_) { return 1 if $f->(); } return 0; } __END__

    See also

    Best regards, Karl

    «The Crux of the Biscuit is the Apostrophe»

    perl -MCrypt::CBC -E 'say Crypt::CBC->new(-key=>'kgb',-cipher=>"Blowfish")->decrypt_hex($ENV{KARL});'Help

A reply falls below the community's threshold of quality. You may see it by logging in.