Beefy Boxes and Bandwidth Generously Provided by pair Networks
XP is just a number
 
PerlMonks  

Conditional idiom : "if X equals Y or Z" ??

by George_Sherston (Vicar)
on Jun 11, 2002 at 10:19 UTC ( [id://173417]=perlquestion: print w/replies, xml ) Need Help??

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

This is a thing I often want to do in different situations. But I'll state the problem in the specific form in which it's just arisen.

I have an array of hashes, call it @tabs. Each hash in @tabs has several keys, including Action and Suppress. Suppress has a default value of 0. Sometimes, however, I want to turn Suppress on (i.e. set its value to 1) depending on the value of Action.

In this example I want to turn it on if Action has one of three possible values, namely GetAll Make or MakeOnly.

The obvious way to do this, and the one I usually end up with, is
for (@tabs) { if ( $_->{Action} eq 'GetAll' or $_->{Action} eq 'Make' or $_->{Action} eq 'MakeOnly' ) { $_->{Suppress} = 1; } }
An arguably more elegant way would be
for my $hash (@tabs) { if (grep {$hash->{Action} eq $_} qw/ GetAll Make MakeOnly /) { $hash->{Suppress} = 1; } }
... but, maybe it's just me, I don't like to get grep out on such a trivial pretext (pass me my twelve bore, there's a snail in the lettuce patch) - plus, it involves creating a new var. Then there's a regex
for (@tabs) { if ($_->{Action} =~ /(^GetAll$)|(^Make$)|(MakeOnly$)/) { $_->{Suppress} = 1; } }
... but that comes with an overhead. Then there's using the logical "or",
for (@tabs) { if ($_->{Action} eq 'GetAll' || 'Make' || 'MakeOnly') { $_->{Suppress} = 1; } }
... which is perfect except that, as eagle-eyed monks will have spotted it DOESN'T WORK, because 'GetAll' always returns a true value so that 'GetAll' || 'Make' || 'MakeOnly' is the same as 'GetAll' .... duh.

As you can see, I've already spent too much time thinking about this. Can some kind monk put me out of my misery, either by telling me there is no simple one line way to do this without additional overhead, or by telling me what it is?

§ George Sherston

Replies are listed 'Best First'.
Re: Conditional idiom : "if X equals Y or Z" ??
by broquaint (Abbot) on Jun 11, 2002 at 10:36 UTC
    Perhaps a quantum solution is needed ...
    use Quantum::Superpositions; ... if($_->{Action} eq any( qw(GetAll Make MakeOnly) )) { $_->{Suppress} = 1; }
    See the Quantum::Superpositions docs for more info on this very deep magic.
    HTH

    _________
    broquaint

Re: Conditional idiom : "if X equals Y or Z" ??
by particle (Vicar) on Jun 11, 2002 at 11:20 UTC
    Quantum::Superpositions is one way. and in perl 6, i believe any and all will be built-in functions. arguably the most basic, and fastest method is to use binary logic.

    my %actions = ( GetAll => 0x001, Make => 0x010, MakeOnly => 0x100, ... ); $_->{Suppress} = 0x001 if ($_->{Action} & ( $actions{GetAll} | $actions{Make} | $actions{Ma +keOnly} );

    ~Particle *accelerates*

      I fail to see how that's supposed to work. $_ -> {Action} is either "GetAll","Make" or "MakeOnly", and not some bitpattern.

      But using a hash leads to one solution:

      my %match = map {$_ => 1} qw /GetAll Make MakeOnly/; ... $_ -> {Suppress} = 1 if $match {$_ -> {Action}};

      Abigail

Re: Conditional idiom : "if X equals Y or Z" ??
by ferrency (Deacon) on Jun 11, 2002 at 13:35 UTC
    You could also do away with the "if" altogether:

    for (@tabs) { $_->{Suppress} ||= $_->{Action} =~ /^(GetAll|Make|MakeOnly)$/; }
    This won't do the match if $_->{Suppress} is already set. And it will only set Suppress if it wasn't set before, and if the Action matches one of the three options. It's also short, though not the shortest possible solution.

    Alan

Re: Conditional idiom : "if X equals Y or Z" ??
by dsheroh (Monsignor) on Jun 11, 2002 at 14:53 UTC
    Then there's always this hashy version:
    my %defaults = (GetAll => 1, Make => 1, MakeOnly => 1); for (@tabs) { $_->{Suppress} = $defaults{$_->{Action}} if exists $defaults{$_->{Ac +tion}}; }
    which behaves the same as any of your examples, although I don't generally consider "default" to mean "value that clobbers any preexisting value"... You might instead want to include all allowed Actions in %defaults and then, when Action is set, do a
    $_->{Suppress} = $defaults{NewAction} if $->{Suppress} == $defaults{Ol +dAction}
Re: Conditional idiom : "if X equals Y or Z" ?? - benchmark
by George_Sherston (Vicar) on Jun 12, 2002 at 12:00 UTC
    In a spirit of inquiry and appreciation for the comments of other monks I ran all the working solutions through Time::Benchmark. Because no test is ever neutral, I've attached the script below for thems as is interested. The overall results are
    10000 trials of abigail (781.550ms total), 78us/trial 10000 trials of broquaint (92.195s total), 9.220ms/trial 10000 trials of esper (676.783ms total), 67us/trial 10000 trials of ferrency (548.917ms total), 54us/trial 10000 trials of george_sherston1 (655.936ms total), 65us/trial 10000 trials of george_sherston2 (852.485ms total), 85us/trial 10000 trials of george_sherston3 (712.300ms total), 71us/trial 10000 trials of samtregar (1.303s total), 130us/trial
    And here's the test script:
    #! /usr/bin/perl -w use strict; use lib '/home/httpd/lib'; use Benchmark::Timer; use Quantum::Superpositions; my %methods = ( george_sherston1 => \&m1, george_sherston2 => \&m2, george_sherston3 => \&m3, broquaint => \&m4, abigail => \&m5, ferrency => \&m6, esper => \&m7, samtregar => \&m8, ); my @tabs = ( { Action => 'GetAll', Suppress => 0, }, { Action => 'Make', Suppress => 0, }, { Action => 'MakeOnly', Suppress => 0, }, { Action => 'foo', Suppress => 0, }, { Action => 'bar', Suppress => 0, }, { Action => 'baz', Suppress => 0, }, { Action => 'goom', Suppress => 0, }, { Action => 'gomp', Suppress => 0, }, ); for my $m (sort keys %methods) { my $t = Benchmark::Timer->new(skip => 1); for(0 .. 10000) { $t->start($m); $methods{$m}->(); $t->stop; } $t->report; } sub m1 { for (@tabs) { if ( $_->{Action} eq 'GetAll' or $_->{Action} eq 'Make' or $_->{Action} eq 'MakeOnly' ) { $_->{Suppress} = 1; } } } sub m2 { for my $hash (@tabs) { if (grep {$hash->{Action} eq $_} qw/ GetAll Make MakeOnly /) { $hash->{Suppress} = 1; } } } sub m3 { for (@tabs) { if ($_->{Action} =~ /(^GetAll$)|(^Make$)|(MakeOnly$)/) { $_->{Suppress} = 1; } } } sub m4 { for (@tabs) { if($_->{Action} eq any( qw(GetAll Make MakeOnly) )) { $_->{Suppress} = 1; } } } sub m5 { my %match = map {$_ => 1} qw /GetAll Make MakeOnly/; for (@tabs) { $_-> {Suppress} = 1 if $match {$_-> {Action}}; } } sub m6 { for (@tabs) { $_->{Suppress} ||= $_->{Action} =~ /^(GetAll|Make|MakeOnly)$/; } } sub m7 { my %defaults = (GetAll => 1, Make => 1, MakeOnly => 1); for (@tabs) { $_->{Suppress} = $defaults{$_->{Action}} if exists $defaults{$_- +>{Action}}; } } sub m8 { my %suppress = map { $_ => 1 } qw(GetAll Make MakeOnly); for (@tabs) { $_->{Suppress} = 1 if exists $suppress{$_}; } }


    § George Sherston
      Your benchmark is flawed. m5, m7 and m8 use a hash so that you can do part of the work only *once*. Setting up the hash each time you are doing tests defeats its purpose.

      Of course, if all you are doing in your program is performing this test once, for the 8 entries in @tabs you shouldn't bother. Optimizing is only useful for hot spots in your code, things you either do many times, or things that are very costly. Trivial checks that will only be done 8 times in your code aren't worth to be optimized.

      Abigail

        Yes, you're quite right - if I were going to use those conditionals more than a few times in any given script I'd set up the hashes once only and spread the overhead. This was more about satisfying my curiosity than about optimisation - in the spirit of that curiosity, here are the results with any hashes involved set once only:
        10000 trials of abigail (494.672ms total), 49us/trial 10000 trials of broquaint (93.071s total), 9.307ms/trial 10000 trials of esper (533.643ms total), 53us/trial 10000 trials of ferrency (547.466ms total), 54us/trial 10000 trials of george_sherston1 (654.023ms total), 65us/trial 10000 trials of george_sherston2 (837.320ms total), 83us/trial 10000 trials of george_sherston3 (715.834ms total), 71us/trial 10000 trials of samtregar (1.022s total), 102us/trial


        § George Sherston
Re: Conditional idiom : "if X equals Y or Z" ??
by axelrose (Scribe) on Jun 11, 2002 at 19:25 UTC
    in SQL one would write
    ... where Action in ("GetAll", "Make", "MakeOnly" )
    This brings me to the idea to write
    if( in( $Action, "GetAll", "Make", "MakeOnly" ) ) { ... } sub in { ... }
    What do you think?
      Your in looks like it might have a for loop inside it. Here's one way to write that up.

      sub in { my ($coderef)= shift(@_); my ($match)= shift(@_); my (@vals)= @_; for ( @vals ) { $coderef->($match) if /$match/ } }
      Notice I had to toss in an extra parameter- a reference to a subroutine.

      Another way to write this up is

      sub in { my ($coderef)= shift(@_); my ($match)= shift(@_); my %dispatch = map { $_ => $coderef } @_; if ( exists $dispatch{$match} ) { $dispatch->($match); } }
      Shrug, it's another way, I guess.

      What do you think?
      I think that I can't look at your sub 'in' without either thinking, "Hey, this has a for loop inside it", or "Hey, this probably uses a dispatch table." :)

      #!/usr/bin/perl use strict; use warnings; sub in_dispatch { my ($coderef)= shift(@_); my ($match)= shift(@_); my %dispatch = map { $_ => $coderef } @_; if ( exists $dispatch{$match} ) { $dispatch{$match}->($match); } } sub in_array { my ($coderef)= shift(@_); my ($match)= shift(@_); my (@vals)= @_; for ( @vals ) { $coderef->($match) if /$match/ } } my $code = sub { my ($pal)= shift(@_); print "Hello $pal!\n" ; }; my @pals = qw(Dick Jane Spot Samuel); in_dispatch($code, 'George', @pals); # no output in_dispatch($code, 'Jane', @pals); # Hello Jane! in_list($code, 'Dick', @pals); # Hello Dick! in_list($code, 'Rover', @pals); # no output
      But before you go using this for any life support systems, make sure you don't mind how both subs handle 'Sam' as the item to test ;)

      Update Added #! line

      blyman
      setenv EXINIT 'set noai ts=2'

Re: Conditional idiom : "if X equals Y or Z" ??
by samtregar (Abbot) on Jun 12, 2002 at 02:03 UTC
    I think I would use:

    my %suppress = map { $_ => 1 } qw(GetAll Make MakeOnly); for (@tabs) { $_->{Suppress} = 1 if exists $suppress{$_}; }

    It's easy to read, fast and short.

    -sam

Re: Conditional idiom : "if X equals Y or Z" ??
by tame1 (Pilgrim) on Jun 12, 2002 at 17:06 UTC
    Everyone has left out Switch.pm. A gift from Damian (of course), it gives us the venerable "switch:case" ability from C.
    use Switch; switch ($val) { case 1 { print "number 1" } case "a" { print "string a" } case [1..10,42] { print "number in list" } case (@array) { print "number in list" } case /\w+/ { print "pattern" } case qr/\w+/ { print "pattern" } case (%hash) { print "entry in hash" } case (\%hash) { print "entry in hash" } case (\&sub) { print "arg to subroutine" } else { print "previous case not true" } }


    What does this little button do . .<Click>; "USER HAS SIGNED OFF FOR THE DAY"

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://173417]
Approved by rob_au
Front-paged by MrCromeDome
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others lurking in the Monastery: (5)
As of 2024-04-24 03:54 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found