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

(Note: Yeah, I know, sounds like homework. Actually this a part of a web-based calendar for a client)

I need to test a variable for several conditions to determine true/false. My code seems "ugly" (to quote the Perl Cookbook) or bloated. Is there a prettier, more efficient way to test many conditions?

For instance: Does today's date falls between 1) a start and stop date, 2) is *not* one of up to 3 holidays, 3) and is one of two dates during the month. I am converting dates to yyyymmdd format before testing. Here's what I have:
if ( $today > $start && $today < $stop ) { if ( $today!= $notthisday[0] ) { if ( $today != $notthisday[1] ) { if ( $today != $notthisday[2] ) { if ( $day == $dayofmon[0] || $day == $dayofmon[1] ) { print "$today is the day."; } } } } }
OR
if ( $today > $start && $today < $stop && $today != $notthisday[0] && $today != $notthisday[1] && $today != $notthisday[2] && $day == $dayofmon[0] || $day == $dayofmon[1] ) { print "$today is the day."; }
Thanks all!

—Brad
"A little yeast leavens the whole dough."

Replies are listed 'Best First'.
Re: Nested testing of many conditions--a better way?
by kesterkester (Hermit) on Mar 23, 2004 at 14:54 UTC
    I'd recommend a refinement of your second technique-- use a single if statement, but break up your conditions into small subroutines with descriptive names.

    if ( date_in_range ( $today, $start, $stop ) && date_not_bad ( $today, @notthisday ) && good_day_of_month ( $day, @dayofmon ) { print "$today is the day"; } sub date_in_range { my ( $today, $start, $stop ) = @_; return $today > $start && $today < $stop; } sub date_not_bad { my ( $today, @notthisday ) = @_; return $today != $notthisday[0] && $today != $notthisday[1] && $today != $notthisday[2]; } sub good_day_of_month ( $day, @dayofmon ) { my ( $day, @dayofmon ) = @_; return $day == $dayofmon[0] || $day == $dayofmon[1]; }
    Untested code, but that gets the idea across.

    Chapters 12 and 14 of Code Complete also talk about various fun methods of optimizing if blocks.

Re: Nested testing of many conditions--a better way?
by BrowserUk (Patriarch) on Mar 23, 2004 at 14:42 UTC

    Update: Added parens around 'or' condition.

    ambrus++ is correct. I should know better than to post without testing.

    It's very much a personal thing, but I'd opt for the latter, but I'd format it like this:

    if ( $today > $start and $today < $stop and $today != $notthisday[0] and $today != $notthisday[1] and $today != $notthisday[2] and ( $day == $dayofmon[0] or $day == $dayofmon[1] ) ) { print "$today is the day."; }

    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "Think for yourself!" - Abigail
      Agreed. The cascade of tests with and is preferable to my eye.

      Just a note that there is a difference between and vs &&. The difference is order of precedence. BrowserUK used the word form so that he didn't have to worry about order of precedence so much. You can also use parentheses with either form to ensure the precedence is correct. The original example is in danger of accidentally mixing up precedence, and getting a completely wrong result.

      A third alternative, in some cases, is the "quick fail." This is a series of reasons to break out of a loop or return from a subroutine early.

      while (<>) { next if /^\s*\#/; # comment next if /^\s*$/; # empty line do_the_work($_); }
      is equivalent to
      while (<>) { if (not /^\s*\#/ and not /^\s*$/) { do_the_work($_); } }

      --
      [ e d @ h a l l e y . c c ]

      I think this code is wrong, as the or operator has a lower precedence than and, so you should either change it to || or parenthisize the last two lines of the condition.

Re: Nested testing of many conditions--a better way?
by flyingmoose (Priest) on Mar 23, 2004 at 14:58 UTC
    if ( $today > $start && $today < $stop ) { if ( $today!= $notthisday[0] ) { if ( $today != $notthisday[1] ) { if ( $today != $notthisday[2] ) { if ( $day == $dayofmon[0] || $day == $dayofmon[1] ) { print "$today is the day."; } } } } }

    I'd have to kill you if you implemented this in my code base :)

    The and-composite is much better and will not draw criticism. By any chance could you put a negated grep in there for the notthisday part to make it a little more Perlish? Also, add some parens rather than relying on your knowledge of the operators as a cautionary technique.

    Halley's quick fail routine is one I like a lot, but I think kesterkester's technique may be better for more complex tests, to a C programmer, that's just extra code to read through (IMHO) -- yet allows you to reuse things at the price of method call overhead. But hey, if you are reusing things, you are better off using a really cool well-tested Date module!.

      Thanks all for great suggestions. I do like kesterkester's subroutine approach because I have lots of tests, some with more conditions than others, so being able to pick and choose is nice (at the expense of all the "wiring" of subroutines). I also like the quick-fail method (I tried this with unless but couldn't get my head around it) of halley, but as applied by dragonchild. And I'm glad flyingmoose won't have to kill me, and let me see what I can do with grep or even map.

      —Brad
      "A little yeast leavens the whole dough."
Re: Nested testing of many conditions--a better way?
by bbfu (Curate) on Mar 23, 2004 at 18:53 UTC

    You might also want to look at Date::Manip's Events_List and Date_IsHoliday, and possibly also ParseInterval, Date_IsWorkDay, etc, as they are specifically designed to make business calendar applications easier.

    bbfu
    Black flowers blossom
    Fearless on my breath

Re: Nested testing of many conditions--a better way?
by dragonchild (Archbishop) on Mar 23, 2004 at 15:33 UTC
    Personally, I would reverse the checks. I'm assuming you're doing this in some sort of loop or block or subroutine. I would do something like:
    sub is_day_ok { my $day = shift; return if $today <= $start; return if $today >= $stop; # Continue as necessary return 1; } if (is_day_ok($today)) { print "$today is the day\n"; } else { print "Nevermind.\n" }

    ------
    We are the carpenters and bricklayers of the Information Age.

    Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose