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

Hello Monks of Perl,

I'm a web designer and I have to write some CGI for an online calendar/schedule.

Part of the form contains 4 pull-down menus which specify the start and end time of an event ( the pull-downs are "start-hour", "start-minute", "end-hour" and "end-minute").

I have to write a sub-routine that tests for the 16 variations of the pull-down menus and only return 1 for the valid states (I've decided to use binary to describe the states):

(0=off 1=on)
Invalid states:
0001
0010
0011
0100
0101
0110
0111
1001
1011
1101
1110

Valid states:
0000
1000
1010
1100
1111

In addition, I also have to verify that the start time and end time are chronologically correct (ie. the start time must be "lesser" than the end time).

I've tried to nest many "if" and "elsif" statements but I'm not able to test all 16 conditions AND verify the start and end times; plus it's very ugly and inefficient code.

I've to complete this code by Monday and I urgently need some help and guidance!!

Any help would be much appreciated, thank you very much.

- softcotton

  • Comment on Testing for 16 possible combinations with 4 pull-down menus

Replies are listed 'Best First'.
Re: Testing for 16 possible combinations with 4 pull-down menus
by idsfa (Vicar) on Dec 10, 2004 at 15:49 UTC

    Screams lookup table to me ...

    sub validate { return ($_[0] < $_[1]) } %lookup = { '0000' => &validate($start,$end), '0001' => false, '0010' => false, . . . '1111' => &validate($start,$end) }; . . . if ($lookup{$switch_binary}) { # Result is valid } else { # Call user a pudding-head }

    The intelligent reader will judge for himself. Without examining the facts fully and fairly, there is no way of knowing whether vox populi is really vox dei, or merely vox asinorum. -- Cyrus H. Gordon
Re: Testing for 16 possible combinations with 4 pull-down menus
by dragonchild (Archbishop) on Dec 10, 2004 at 14:59 UTC
    You probably want to look at one of the DateTime modules on CPAN for this. I would recommend DateTime. It's a bit hefty to read, but it has everything you need, plus the documentation is really good.

    As for testing the menus ... You have 5 valid states. Either you're in a valid state or you're not. Test for the five valid states using if-elsif, and else for your invalid states. Something like:

    if ( --VALID STATE 1, 0000--) { # Do something here } elsif ( --VALID STATE 2, 1000--) { # Do something here } elsif ( -- And so on for your 5 valid states) { } else { # This is where your 11 INVALID states drop down. # Does it really matter *which* invalid state it is? }

    If you need more help, please don't hesitate to ask for clarification.

    Being right, does not endow the right to be rude; politeness costs nothing.
    Being unknowing, is not the same as being stupid.
    Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
    Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

Re: Testing for 16 possible combinations with 4 pull-down menus
by gaal (Parson) on Dec 10, 2004 at 15:10 UTC
    Let's do this top-down.

    if (valid_state($pull_downs) && $start_time < $end_time) { # okay; put processing here } else { # not okay: yell at user. }

    For this to work, we need to satisfy some conditions. First, we need to bump all the pull-downs together; and second, we need the start and end times to be in some comparible format. Fortunately, these are both easy tasks. Then we have to write the valid_state functions, which isn't hard either.

    # put this before the if mentioned above my $pull_downs = join "", $pull_down1, $pull_down2, $pull_down3, $pull +_down4; # or, if you're using CGI.pm, something like this: my $pull_down = join "", map { $query->param("pull_down$_") } 1 .. 4; # (whatever works; just mash them together. $pull_downs now looks like + your binary numbers. # And this returns true only if the number is one of your valid ones: { my $valid = qr/^(0000|1000|1010|1100|1111)$/; sub valid_state { return shift =~ $valid; | }

    You may need to do a little extra work to binarify the data from the pull-down to your internal format, but that shouldn't be too daunting by now. As for the times, a good way of making them compatible is to translating them (say, with DateTime) to epoch seconds. These are simply two numbers, the bigger one represents a later time.

Re: Testing for 16 possible combinations with 4 pull-down menus
by ikegami (Patriarch) on Dec 10, 2004 at 15:51 UTC
    If you try to simplify it to an equation, you end up with
    $is_valid = ($a ? ($d ? $b && $c : !$b && !$c || !$b && $c || $b && !$ +c) : !($b && $c && $d);
    which is totally unreadable and unmaintainable, doubly so when you use the full variable names. So don't do this :)
Re: Testing for 16 possible combinations with 4 pull-down menus
by BrowserUk (Patriarch) on Dec 10, 2004 at 15:57 UTC

    Use a hash as a state table.

    You'll have to fill in how you get your 0s and 1s and concatenate them, and getting your times into comparable format (seconds since...).

    #! perl -slw use strict; my %states = ( 0000 => 1, 0001 => 0, 0010 => 0, 0011 => 0, 0100 => 0, 0101 => 0, 0110 => 0, 0111 => 0, 1000 => 1, 1001 => 0, 1010 => 1, 1011 => 0, 1100 => 1, 1101 => 0, 1110 => 0, 1111 => 1, ); my $state = ....; ## Get state from wherever? if( $states{ $state } ) { ## Validate values my( $startTime, $endTime ) = ...; ## Get times if( $startTime < $endTime ) { ## Do it } else { ## Reject. } else { ## Reject. }

    BTB. If specifying just start and end hour is valid, why isn't specifying start hr/min and just end hr (1110) also valid? Eg. Start:10:45 * End: 11


    Examine what is said, not who speaks.        The end of an era!
    "But you should never overestimate the ingenuity of the sceptics to come up with a counter-argument." -Myles Allen
    "Think for yourself!" - Abigail        "Time is a poor substitute for thought"--theorbtwo         "Efficiency is intelligent laziness." -David Dunham
    "Memory, processor, disk in that order on the hardware side. Algorithm, algorithm, algorithm on the code side." - tachyon
Re: Testing for 16 possible combinations with 4 pull-down menus
by waswas-fng (Curate) on Dec 10, 2004 at 16:34 UTC
    Are you gaining anything by using binary numbers to describe the states? If you can't simplify lookups or checking with bitwise operations on the states then just use a plain english lookup table -- simplify your code and make it more readable.


    -Waswas
Re: Testing for 16 possible combinations with 4 pull-down menus
by tilly (Archbishop) on Dec 11, 2004 at 02:53 UTC
    It is just a question of organizing the facts to test in a convenient way that can be tested without too much muss or fuss. The following untested validation function should do it:
    # Tests the start_hour, start_minute, end_hour, and end_minute # pulldowns to verify that a consistent set of them have been # specified, and that the start time (if specified) is before # the end time. sub validate_date_pulldowns { if (defined(param('end_hour'))) { return 0 unless defined(param('start_hour')); return 0 if param('start_hour') > param('end_hour'); } if (defined(param('start_minute'))) { return 0 unless defined(param('start_hour')); } if (defined(param('end_minute'))) { return 0 unless defined(param('start_minute')); return 0 unless defined(param('end_hour')); return 0 if param('start_hour') == param('end_hour') and param('start_minute') > param('end_minute'); } return 1; }
    It would be easy to extend this code sample to add in support for start/end seconds as well.

    Update: Note that the key idea is my strategy of turning many combinations of conditions into a set of "guard conditions" on which you exit early. I've found that this strategy often converts deep nested decision trees into a series of fairly straightforward decisions (as above).

      Toward this purpose I created Set::CrossProduct. Instead of figuring out all the combinations, I have a bunch of anonymous arrays that specify possible values for each input. The module handles everything and hands me one set of inputs at a time. You call them "guard conditions" and I call them "boundary conditions".

      In this case, there are only 16 combinations, but I've been able to work with thousands of combinations without making the test code huge.

      --
      brian d foy <bdfoy@cpan.org>
        Automation is good. It would have avoided the bug that I had in my code above. (If end_minute is not defined, you aren't allowed to have both start_minute and end_hour defined.)
Re: Testing for 16 possible combinations with 4 pull-down menus
by mattr (Curate) on Dec 11, 2004 at 17:58 UTC
    I don't understand why you are using pulldowns with only one valid item in each. So I am assuming you are doing preprocessing we don't know about.

    Binary isn't normally good for scheduling (I've done a bunch with pulldowns recently) because what if you want to add quarter-hour resolution, etc. Anyway, assuming we have to deal with this binary code and you have it nicely padded so it is always 4 digits. So we have yummy binary! This says to me:

    Valid states:
    0000 0
    1000 8
    1010 10
    1100 12
    1111 15

    So I parsed the binary and broke it into bits. Intentionally left no comments so you can learn from it in case this is homework. As for bounds checking, if you only need to check time0 < time1 then why not convert hours to minutes? (Not done here)

    #!/usr/bin/perl -w use strict; my $str; my @test = ("0000","0001","0010","0011", "0100","0101","0110","0111", "1000","1001","1010","1011", "1100","1101","1110","1111"); my @accepted = (0,8,10,12,15); for my $t (@test) { print "Input: $t\tTested: "; print "[" . parsebin($t) . "] " ; print validinput($t,\@accepted); print "\n"; } sub validinput { my $in = shift; my $r = shift; return 1 if xinset(parsebin($in),$r); return 0; } sub xinset { my $x = shift; my $set = shift; #print "X $x SET" . join(" / ",@$set). "\n"; for my $y (@$set) { return 1 if $x == $y; } return 0; } sub parsebin { # parse a string of ones and zeroes my $str = shift; my $total = 0; my $power = 0; for my $i (1..length($str)) { $total += 2 ** $power if substr($str,-1 * $i,1); $power++; } return $total; }
Re: Testing for 16 possible combinations with 4 pull-down menus
by JamesNC (Chaplain) on Dec 12, 2004 at 04:39 UTC
    I don't think this is a job for Perl. I would write some javascript to do this and get rid of the pull downs. Make your javascript robust enough to handle various inputs. Once you build some date and time validators, you can reuse it. I do this for my customers with date and time fields. First, you want your customers to have instant feed back.(Be interactive when it needs to be - errors, prompting for missing fields, etc. ) Second, round trips to your script is unneccessary. Third, your content will be more user friendly to your customer.
    JamesNC
      Anything done in Javasript in a browser can and will be subverted by a malicious user, or will break on certain browsers, or will simply not be executed when users have disabled Javascript, or will get blocked by content filters, or ...

      Sure, use Javascript if you want to be nice and friendly and give instant feedback on errors, but TEST TEST TEST so that it doesn't break hideously in browsers other than the one you habitually use. And even then, you must also validate the data on the server, lest you have a nasty user like me trying to break your data. Of course, using Javascript means you need to do at least twice as much work - you need to write and test and maintain Javascript as well as writing and testing and maintaining perl. Does your employer really want you to do that?

      A reply falls below the community's threshold of quality. You may see it by logging in.