Beefy Boxes and Bandwidth Generously Provided by pair Networks
Come for the quick hacks, stay for the epiphanies.
 
PerlMonks  

Comparing against multiple values

by doran (Deacon)
on Mar 18, 2002 at 19:14 UTC ( [id://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
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://152562]
Approved by root
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others surveying the Monastery: (4)
As of 2024-03-29 12:56 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found