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

Hi

my problem is that I have to read in a record, and dependant on fields in that record equalling (or not) a set of other values, I construct a string.

One such clause is as follows:

if ($a eq "A" && ($b eq "C" && ($p != 1 )) && ($s ne "P" )) { return "12341"; }
another clause is this:
if ($a eq "A" && ($p == 88 || $p == 89 || $p == 90 || $p == 91) ) { return $p; }
ad nauseam. Indeed, in another function, I have a string of || clauses 50 or 60 long ...

The variables $a and $b are constant to most of the clauses, but other variables can be checked just to keep me on my toes.

I'd like to make the process as simple (and as obvious) as possible because the team I program for has mixed skills, from nearly nothing to not much.

TIA

Replies are listed 'Best First'.
Re: Conditional Elimination
by cdarke (Prior) on Aug 30, 2011 at 11:01 UTC
    First, you should not be using variables $a and $b because these are magic variables used in sort. More importantly they are probably meaningless in your context.

    Anyway, you might be better off using a regular expression (RE) to check patterns rather than literal values. I say 'might' because it really depends on the application, and REs can be slow. This is a balance decision which only someone with complete knowledge of the application can make.
    A reply falls below the community's threshold of quality. You may see it by logging in.
Re: Conditional Elimination
by RichardK (Parson) on Aug 30, 2011 at 11:56 UTC

    If you have a long list of items to check $p against, you could try something like

    my @ptests = (88,89,90,91,110,115); if ( grep {$p == $_} @ptests ) { return $p; }

    Then it's easy to add new items to the list and the code does not need to change.

    or you could use List::MoreUtils firstval which may be slightly more efficient if you have long lists

    use List::MoreUtils qw {firstval}; my @ptests = (88,89,90,91,110,115); if ( firstval {$p == $_} @ptests) { return $p; }

      If you have 5.10 or newer perl, I would use the smart match operator using the same logic. Smart match generally benchmarks significantly faster than grep and first.

Re: Conditional Elimination
by GrandFather (Saint) on Aug 30, 2011 at 11:21 UTC

    Show us a fragment of real code, or if that is real code then give your variables real names! Better still, describe what you are trying to achieve with your script using something representative of your real data and code. Very likely your problem can be attacked in an elegant, or at least maintainable, fashion by tackling it from a completely different direction, but we can't really help with that unless you fill in some of the bigger picture for us.

    True laziness is hard work
Re: Conditional Elimination
by bart (Canon) on Aug 30, 2011 at 11:53 UTC
    Funny you should ask this now, as I just saw a very similar question on StackOverflow but for PHP, yesterday (posted 23 hours ago). Do you, by accident, know each other?

    Anyway, what I'd think of is to store a simple expression, as text, in the database. You could do it in Perl and use eval. Or, my preference, you could write the expressions in a simpler language (which first of all doesn't have the '$' for the variables) than Perl, for example Javascript (and C and ... zillions of other languages), possibly even extended. For example your code could better, IMHO, be rewritten as:

    a eq "A" && p in (88, 89, 90, 91)

    Then all you have left to do is write a simple expression parser/evaluator, and I just happen to have written one, once. I've even converted it to Perl, here: Operator Precedence Parser. I see it doesn't parse functions (with parameters) (yet), which would be useful for implementing in(...), but that's not too hard to add. I might do that as an exercise, later today. <pEdit When I think about it, using/parsing "in" as a function is not right: it's actually an operator with a scalar on the LHS and a list on the RHS. A function with arguments would look similar, but would be used in a different position (as an expression instead of as an operator) so it ought to be parsed, and processed, differently.

      write the expressions in a simpler language

      I agree. 50 conditions calls for a domain-specific language to separate the building of the conditions from their execution. Decision::ParseTree looks like another good place to start.

Re: Conditional Elimination
by JavaFan (Canon) on Aug 30, 2011 at 11:34 UTC
    Except for formatting of the first example, I'd leave it as is. I'd format the first one as:
    if ($a eq "A" && $b eq "C" && $p != 1 && $s ne "P") { return "12341"; }
    There may be all kinds of ways to condense the clauses, but while that may save on the number of lines, just listing them is probably the simplest, and easiest to understand.

    The latter example could be written as

    if ($a eq "A" && $p >= 88 && $p <= 91) { return $p; }
    but I'd only write that if it's really intended that $p is inside a range (and an integer), instead of one of 4 values that just happened to be in a row.
Re: Conditional Elimination
by shawnhcorey (Friar) on Aug 30, 2011 at 14:56 UTC

    Put the conditions in a sub. Then you can test things piece-wise with multiple ifs. Arrange them so that the first if will most likely return a fail. This makes the sub decide early and speeds up the code.

    sub some_test {
      my ( $a, $b, $p, $s ) = @_;
    
      return 0 if $a ne 'A';
      return 0 if $b ne 'C';
      ...
    }
    
      Your speed argument doesn't make any sense. Really, you think calling a sub and hand rolling your own short circuiting beats the build in short circuiting of && and ||?

        No, I think using a sub makes it easier to read. Making those tests that will return early will not slow it down as much as putting them in random order. It's just something to keep in mind when writing the sub. But the most important thing is to put the condition in a sub so that it's easier to understand.

Re: Conditional Elimination
by locked_user sundialsvc4 (Abbot) on Aug 31, 2011 at 01:04 UTC

    Two approaches that I have observed:

    1. Put the desired elements into a hash, with a value (say...) of 1, then look for each value.   If it’s there, the result will be True, otherwise (undef) it will be False.
    2. Use a regex, e.g. if ($foo =~ /^(12|345|67)$/) ...   Notice how the ^ and $ serve to bind the match to both the beginning and the end of the string, hence to “the value of the entire string.”
    3. Use a sub.   You can get as messy as you like in a sub, so to speak, because you only get messy once.

    As far as I am concerned, the number-one priority is that the logic should be crystal clear, as well as maintainable.

Re: Conditional Elimination
by pileofrogs (Priest) on Aug 30, 2011 at 23:35 UTC

    Like everyone has said, it depends heavily on your particular situation. I'm going to mention an approach that you almost certainly should NOT use. If clarity is important, DO NOT DO THIS. But it's fun, so I'm going to try to write about it.

    You can pack the variables into fewer variables.

    Lets say you have two variables A and B. Each can have a value from 0 - 99. Then we could multiply Ax100 and add it to B. Then we have 1 variable instead of two but it contains all the information of the 2 original variables and you can check it easily in one operation.

    To continue the example, Lets say if A=34 and B=75 the world explodes. All we have to do is our little operation and then see if it equals 3475.

    This is the same thing you see with bit maps for things like file permission modes.

    Again, this is NOT AT ALL recommended for clarity. It's just another way of thinking of a problem like this.

    It's also probably not faster than a whole buncha == && == &&. I think it would depend on how many variables, how many iterations it goes through etc...

Re: Conditional Elimination
by Anonymous Monk on Aug 30, 2011 at 11:33 UTC
    If i got it right, in the second clause you want to get a range for the condition to be valid(88 to 90). In which case you could replace:  ($p == 88 || $p == 89 || $p == 90 || $p == 91) For:    (88<= $p && $p <=90)
    And that sould do what you need, since you seem to need to do the same thing for whatever value of $p between 88 and 90. Hope it helps.
      What if $p = 91? Or $p = 88.7?
Re: Conditional Elimination
by Anonymous Monk on Aug 30, 2011 at 21:25 UTC
    %options = map {$_ => 1} 88, 89, 90, 91; if( $a eq 'A' && $options{$p} ) { ... }
Re: Conditional Elimination
by Nomad (Pilgrim) on Sep 01, 2011 at 07:48 UTC

    Conditional Elimination: it can't be done. All you can do is to move the conditionals to somewhere else and to make it clear what your code is doing. It's always worth remembering that:

    Programs must be written for people to read, and only incidentally for machines to execute.

    Which begs the question 'how do you do this?' There are a few possibilities.

    • Refactoring - examine your code closely and see whether there are any recurring patterns in the logic, if 'yes' spin them off into subroutines.
    • Dispatch table - you may be able to improve readability by using a dispatch table.
    • Callbacks - you may be able to improve readability by using callbacks
    • Multiple dispatch - if you are using OO, multiple dispatch might improve readability

    Of course, each of these depends on what the code actually does, and how it can be simplified. IME, and all things being equal (which they rarely are), I'd probably try dispatch tables first.

    I'd just like to reiterate that, in each of the suggestions above, you are not eliminating the conditions, you're just moving them somewhere else in the code.

    As an aside, I'd like to throw in that, doing any of this takes time. Making code beautiful can take lots of time and we all have limited resources. So you might want to ask yourself, 'is it worth it?'

Re: Conditional Elimination
by dunnyman (Novice) on Sep 02, 2011 at 11:12 UTC
    I have a record of a certain number of fields, I know the fields, but I only know the field values in the code; the total set of values for each field is unknown

    The way I had originally planned was a large hash array, with the field values implemented in the hash, and the value I want held in the hash:

    my $h={ j=>{b=>"some value meaningful to another part of code"}};
    so
    return $h->{$l_id}->{$letter_code} if (defined $h->{$l_id}->{$letter_code});
    however, this becomes difficult when you have a value
    my $h = {j=>{b=>{c=>"some other meaningful value"}}};
    but this
    my $h = {j=>{b=>{c=>1}}};

    makes the code mysterious even to me, and I'm inventing the damn thing.

    The other problem is that I have a few != clauses; I can't model these in the hash because I don't know the total set of values for that group. TIA

Re: Conditional Elimination
by dunnyman (Novice) on Sep 02, 2011 at 11:25 UTC
    The varible names in the code-fragments do not reflect the names in the program. Names have been changed to protect the innocent!
Re: Conditional Elimination
by dunnyman (Novice) on Sep 02, 2011 at 13:26 UTC
    The 4th option is to use a rule engine running off a couple of tables. It's worth it in this case because I have soo many clauses.
Re: Conditional Elimination
by pileofrogs (Priest) on Sep 02, 2011 at 23:13 UTC

    Okay, I know this thread is kindof stale by now, but I had another thought I wanted to add.

    If performance is important, you could write a script that writes your script. The actual script that computes the data would be unreadable but that wouldn't matter so much because you could make the script-that-creates-the-script as readable and slow as you like. You could even separate out the crazy 50 clause recipes into their own config file that non-coders can understand and maintain. Or! Say you've got a zillion recipes in an excell file or web page or whatever. Make it part of your process to parse that to create your config to create your actual script. (Having the intermediate step of creating a config file can help make it possible to check that the parsing of the original data is working correctly).

    Okay! I know this thread was dead before I got here and no one is going to read this! Woo hoo!

    Eat More Cake

    --Pileofrogs

      Cheers - a good option. I'll give the database a try first.