Beefy Boxes and Bandwidth Generously Provided by pair Networks
Welcome to the Monastery
 
PerlMonks  

Comparing against multiple values

by doran (Deacon)
on Mar 18, 2002 at 19:14 UTC ( #152562=perlquestion: print w/replies, xml ) Need Help??
doran has asked for the wisdom of the Perl Monks concerning the following question:

It's embarrasing asking what seems like an elementary question, but I've looked around and didn't see anything.

Frequently I need to check if a value matches any number of values. A common solution, cited in the camel book is to use a regex, seperating the search strings with a vertical bar:

if ($foo=~/$bar|$baz|$boo/){ print "yep!\n"; }
While that's very easy, it's not as fast as using a comparison operator such as 'eq' or '=='. However using these often leads to redundant and cumbersome looking code:
if (($foo eq $bar) || ($foo eq $baz) || ($foo eq $boo)){ print "yep!\n"; }
or the much malined:
if ($foo eq $bar){ print "yep!\n"; } elsif ($foo eq $baz){ print "yep!\n"; } elsif ($foo eq $boo){ print "yep!\n"; }
Is there way of using a single comparison operation against several values, grouping them together much as you can do with a single regex statement, without the overhead of the regex?

Replies are listed 'Best First'.
Re: Comparing against multiple values
by PrimeLord (Pilgrim) on Mar 18, 2002 at 19:19 UTC
    I am not sure if it would be faster or not, but you could always store each of the match terms as keys in a hash and do something like this.

    my %hash = qw( $bar => 1 $baz => 1 $boo => 1 ); print "yep!\n" if exists $hash{$foo};


    HTH

    -Prime

    Update: I tested the code and it is considerably faster. The first benchmark time is the regex solution and the second benchmark time is using a hash. Both tests were run 100,000 times.

    Regex Solution timethis 100000: 18 wallclock secs ( 0.41 usr + 0.05 sys = 0.45 CPU) Hash Solution timethis 100000: 1 wallclock secs ( 1.58 usr + 0.00 sys = 1.58 CPU)
      Maybe you want to score points for obfuscation:
      if ({map{$_=>1}($bar,$baz,$boo)}->{$foo}) { print "Yes!\n"; }
      A dirty, and almost as quick way is:
      if (grep{$_ eq $foo} $bar, $baz, $boo) # ...
      If you are comparing frequently, you might want to have a persistent hash which you can refer to on a regular basis. Why create it every time if it is the same?
Re: Comparing against multiple values
by larsen (Parson) on Mar 18, 2002 at 19:29 UTC
Re: Comparing against multiple values (boo)
by boo_radley (Parson) on Mar 18, 2002 at 19:36 UTC
    While that's very easy, it's not as fast as using a comparison operator such as 'eq' or '=='. However using these often leads to redundant and cumbersome looking code:
    have you met my friend, grep?

    my @values= qw(foo bar baz bax); my $test = "foo"; print "woo\n" if (grep {$_ eq $test}@values) ;
    This is probably not the hottest use for grep if you've got a long list of values, since it'll build an array of values matching the test only to use the result in a scalar fashion. It'd be handy if you could last in a grep in this particular instance.
    You could produce the same behavior in a for loop quite easily that would exit on the first found value.
Re: Comparing against multiple values
by buckaduck (Chaplain) on Mar 18, 2002 at 19:45 UTC
    Could be a candidate for the Switch module:

    use Switch; switch ($foo) { case [$bar,$baz,$boo] { print "yep!\n" } }

    buckaduck

Re: Comparing against multiple values
by dws (Chancellor) on Mar 18, 2002 at 19:34 UTC
    While that's very easy, it's not as fast as using a comparison operator such as 'eq'

    If you're truly concerned about speed, use a /o modifier to compile the regex once, rather than re-interpolating the variable each time the regex is fired.

    You'll also need to fix the regex to avoid false positives. Here's how I do it:

    my $re = "\A(?:" . join("|", $foo, $bar, $baz) . ")\Z"; ... if ( /$re/o ) { ... }

Re: Comparing against multiple values
by AidanLee (Chaplain) on Mar 18, 2002 at 23:07 UTC

    PrimeLord's hash suggestion is quite good. I'd suggest also maybe a for loop as something more traditional:

    for( qw( foo bar baz ) ){ print "yep" and last if $test eq $_; }
    or all on one line:
    print "yep" and last if $test eq $_ for( qw( foo bar baz ) );
    if it'll be a more complex action down the line, you can "do":
    do { print "yep"; last; } if $test eq $_ for( qw( foo bar baz ) );
Re: Comparing against multiple values
by shotgunefx (Parson) on Mar 18, 2002 at 20:02 UTC
    If the result is to call some sub then I would just use a hash but sometimes I use a little snippet like so.
    #!/usr/bin/perl sub matches(&;@) { my $coderef = shift ; my @data = @_; for (my $i=0; $i <= $#data; $i++ ){ $_ = $data[$i]; if (&$coderef){ return wantarray ? ($_ ,($i ) ) : $_; } } return; } my @actions = qw(get fetch store info print); if (my ($v,$i) = matches { $action eq $_ } @actions ){ print "Found $v at $i\n"; } # Or if (my $v = matches { $action eq $_ } @actions ){ print "Found $v $i\n"; } # Or die "Damn! Invalid action $action" unless matches{$action eq $_ } @act +ions;


    -Lee

    "To be civilized is to deny one's nature."
Re: Comparing against multiple values
by flocto (Pilgrim) on Mar 18, 2002 at 21:38 UTC
    There are a few solutions that came to my mind:
    my $code = "sub check { my \$val = shift; if ("; $code .= join (' or ', map { "\$val eq \"$_\""; } (@ceck_vals)); $code .= ") { return 1; } else { return 0; } }"; eval $code;
    Propably quite performent if you have to check for these values very often and they never change (CGIs, for example..)
    ---
    my %check = map { $_ => 1; } (@ceck_vals); if ($check{$val}) { do_foo (); }
    Propably the better choice if @check_vals often changes..
    ---
    if (grep { $val eq $_ } (@check_vals)) { do_foo (); }
    I expect it to be kind of slow..
    ---
    my $regex = join ('|', @check_vals); if ($val =~ m/$re/) { do_foo (); }
    This is slow, but it can match parts of $val, in case this is needed..
    ---
    There are more, but these were the ones I like the most :) Have fun ;)
    Regards,
    octo

    P.S.: I didn't do ANY performance tests, so my guesses are likely to be wrong, please don't rely on this!
Re: Comparing against multiple values
by traveler (Parson) on Mar 18, 2002 at 22:04 UTC
    You could also use Meta::Ds::Hash. Something like this (untested):
    use Meta::Ds::Hash; my($hash)=Meta::Ds::Hash->new(); $hash->insert($bar); $hash->insert($baz); $hash->insert($boo); # later if($hash->has($foo)) { print "Found it\n"; }
    It would be interesting to compare these various solutions for large and small numbers of comparison sets.

    HTH, --traveler

      i don't know how to reply to the whole thread so i have used the last answer, sorry for that. if ($foo=~/$bar|$baz|$boo/){ is not strictly equivalent to if (($foo eq $bar) || ($foo eq $baz) || ($foo eq $boo)){ as =~ will be true for $foo=abc and $boo=ab but "abc" will not be eq to "ab" if you want them to be strictly equivalent then it will be something like : if ($foo=~/^$bar$|^$baz$|^$boo$/){ i have tested with string values but not with $var so maybe there is some \ to use at the good places for this to work regards.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://152562]
Approved by root
help
Chatterbox?
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others chilling in the Monastery: (4)
As of 2018-05-25 03:48 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Notices?