[Neworleans-pm] Fwd: Solutions and Discussion for Perl Quiz of the Week #22

E. Strade, B.D. estrabd at yahoo.com
Tue Aug 31 14:02:29 CDT 2004




=====
http://www.brettsbsd.net/~estrabd

__________________________________
Do you Yahoo!?
Yahoo! SiteBuilder - Free, easy-to-use web site design software
http://sitebuilder.yahoo.com

----- Original message -----
From: "Jose Alves de Castro" <jcastro at telbit.pt>
To: perl-qotw at plover.com
Date: Tue, 31 Aug 2004 14:56:18 -0400
Subject: Solutions and Discussion for Perl Quiz of the Week #22


Sample solutions and discussion
Perl Quiz of The Week #22 (20040825)


        The purpose of this problem is very simple (and hopefully
        something many of us will be able to use).

        Inside a directory (say $ENV{HOME}/.upcoming) we have several
        files.  Here's part of one:

        02/26   l\xE9on brocard
        03/06  michelangelo
        05/29   simon cozens
        12/28   randal schwartz
        02/27 eduardo nuno
        03/05 crapulenza tetrazzini
        03/16 richard m. stallman 


        This particular file is appropriately named 'birthdays'. You
        can have as many different files as you wish in that
        directory.

        Here's part of another file, 'events':

           01       payday
           15       payday
        08/13/2004  slides for YAPC::EU::2004
        03/01       feast of st. david
        03/01/1565  Rio de Janeiro founded        
        03/09/2004  dentist appointment 10:00

        As you can see, both the month and the year are optional. When
        not given a month, we'll have three spaces; by the end of the
        date we may have as many spaces and/or tabs up to the
        description.  The 'events' file says that payday occurs on the
        1st and 15th of every month, and that the Feast of St. David
        occurs each year on the first day of March.

        This week's problem consists of writing the script 'upcoming',
        which tells us about our upcoming events.  Suppose today is 26
        February.  Then the output will contain:

                birthdays
                   ===> 02/26      l\xE9on brocard
                   -->  02/27      eduardo nuno

                events
                -->     03/01   payday
                -->     03/01   Feast of St. David


        Explanation:

        * For each file, you get a paragraph, if there are upcoming
          events mentioned in that file.

        * The program will print all the events that will occur in the
          next 'n' days, where 'n' is specified with a '-n'
          command-line flag.  If '-n' is omitted, 'n' will default to
          7 days.

        * For each event, you get a string that tells you about the
          event's proximity:

          0 =>  '   ===>',
          1 =>  '   -->',
          2 =>  '  -->',
          3 =>  ' -->',
          4 =>  '-->',
          5 =>  '->',
          6 =>  '>',
          7 =>  ' ',

        If the '-n' switch is given, and the event is in the specified
        range, but more then 7 days ahead, then the proximity string
        is something like (8) or (13) depending on how many days ahead
        the event is.  Here we're running the program on 26 February,
        as before, but with the option '-n 12':

                birthdays
                   ===> 02/26      l\xE9on brocard
                   -->  02/27      eduardo nuno
                (8)     03/05   crapulenza tetrazzini
                (9)     03/06   Michelangelo

                events
                -->     03/01   Feast of St. David
                -->     03/01   payday
                (12)    03/09   dentist appointment 10:00


        Note that the founding of Rio de Janeiro did not occur in
        either output, since it has already passed.

        As you'll notice, it's a little hard to schedule things such
        as the fourth Thursday of each month, or dates like Mother's
        Day (I don't know about the rest of the world, but that
        changes, here in Portugal).  It might be a good idea to find a
        reasonable way to solve this.

        Happy hacking :

----------------------------------------------------------------

This week's problem had four submissions, by Roger West, Zed Lopez
Dave Cash and Mark Dominus. Thanks, guys :-)

[ Code for the four solutions can be found at

        http://perl.plover.com/qotw/misc/r022/

  - MJD ]


Their four solutions have different ways of solving the problem, which
we'll discuss below.

Though the problem wasn't all that hard, something terrible happened...
it was solved in mid August, and tested by the end of August... and
you'll see in a moment what happened because of that :-)


First, let's talk about input. The problem stated:

> both the month and the year are optional

Here are the four types of dates this would allow for:

09/01/2004      should pass 1
09/01           should pass 2
   01/2004      should pass 3
   01           should pass 4

As you'll notice, the first date has month, day and year, the second
does not contain the year (optional), the third does not contain the day
(optional) and the last one has only the day (thus not including both
optional parameters).

For a test suite regarding dates, here's what I used:

09/01/2004      should pass 1
09/01           should pass 2
   01/2004      should pass 3
   01           should pass 4
09/01/2003      should NOT pass a
09/01/2005      should NOT pass b
08/01           should NOT pass c
09/11           should NOT pass d
   01/2003      should NOT pass e
   01/2005      should NOT pass f
   29           should NOT pass g
   18           should NOT pass h

You'll notice that all this input is valid. Given that I ran the tests
on August 30th, all of the first four tests should be displayed. As for
the others, none of them should. Tests a,c,e,g contain dates that have
already passed, while tests b,d,f,h all have dates that are not in the 7
days range from today (Aug 30th). Here are the results for these tests:


      1 2 3 4 a b c d e f g h
roger _ _ x _ _ _ _ _ _ _ _ _
zed   _ _ E x _ _ _ _ E E _ _
dave  _ _ _ _ _ _ _ _ _ _ x _
mark  _ _ _ _ _ _ _ _ _ _ _ _

_ - the entry "was displayed" for test 1-4 or "wasn't displayed" for a-h
x - the entry "wasn't displayed" for test 1-4 or "was displayed" for a-h
E - fatal error


As you can see, most of the solutions had one problem or another (hey,
dates are tricky...)

[ I also wonder what these programs would have done on December 30 for
  dates that occur the following January.  I was at some pains to get
  this right, but I think it's a subtle point.  For example, when your
  program sees "09/11" it's tempting to have it assume that the year
  defaults to *this* year, but in the case of "01/11" it should
  default to *next* year, unless the current date is in early
  January.  - MJD ]

Let's see what went wrong:


Roger's Solution:

Test 3: Roger's code has five different regexps to get all the possible
cases... here's what they do:

first  - catches dates having only the day
second - catches complete dates
third  - catches dates without the year
fourth - ??
fifth  - ??

I gave up at trying to understand what the last two ones did, but I
would bet it has something to do with the "2nd tuesday of every month"
format... (am I wrong?)

There doesn't seem to be any regexp to catch dates without the month,
but with the year...


Zed's Solution:

Test 3: Module Date::Calc produced an error with this test. To
comprehend the problem, take a look at this regexp:

m|^\s*(\d+)(?:/(\d+))?(?:/(\d*))?\s+(.*)$|

Here's the problem: since no month was provided, $1 (given to $month)
captures the day instead of the month. That would be OK, as later on, if
$day has no value, $day gets the value of $month and $month another
value... the problem is that this regexp puts the day in the month and
the year in the day. Since it has no year, $year gets the value of the
current year, and hence we have day, month and year, but what we really
have for day is the month, and for the month, the year; we have this
date: 2004/01/2004. Date::Calc, as it should, complains about this.

Test 4: It fails because the month, if not available, is replaced with
the current month. Given that I did the test on one of the last days of
August, the date was considered as being 08/01/2004, which had indeed
already passed. 09/01/2004 wasn't considered, but should have been.

Test e: Same thing as test 3.

Test f: Same thing as test 3.


Dave's Solution:

Test g: For some strange reason, Dave's solution regards yesterday's
events as today's ones :-| It still does consider today's events as it
should, though. Since Dave said nothing about this (at least that I
remember of), I don't consider this a feature :-) I had nor time nor
skills to discover the reason for this, but I sure would like to know...
:-)



Now that we've dealt with the input, let's take a quick look at the
output. I tested with this:

08/30/2004      today
08/31/2004      in 1 day
09/01/2004      in 2 days
09/02/2004      in 3 days
09/03/2004      in 4 days
09/04/2004      in 5 days
09/05/2004      in 6 days
09/06/2004      in 7 days
09/07/2004      in 8 days
09/08/2004      in 9 days
09/09/2004      in 10 days

Here's what I got:

Roger:

birthdays
   ===> today
   -->  in 1 day
  -->   in 2 days
 -->    in 3 days
-->     in 4 days
->      in 5 days
>       in 6 days
        in 7 days

Zed:

birthdays
   ===> 08/30 today
   --> 08/31 in 1 day
  --> 09/01 in 2 days
 --> 09/02 in 3 days
--> 09/03 in 4 days
-> 09/04 in 5 days
> 09/05 in 6 days
 09/06 in 7 days

Dave:

birthdays:
     ===> 08/30   today
     ===> 08/31   in 1 day
     -->  09/01   in 2 days
    -->   09/02   in 3 days
   -->    09/03   in 4 days
  -->     09/04   in 5 days
  ->      09/05   in 6 days
  >       09/06   in 7 days
          09/07   in 8 days

Mark:

birthdays:
   ===> 8/30/2004 today
   -->  8/31/2004 in 1 day
  -->   9/ 1/2004 in 2 days
 -->    9/ 2/2004 in 3 days
-->     9/ 3/2004 in 4 days
->      9/ 4/2004 in 5 days
>       9/ 5/2004 in 6 days
        9/ 6/2004 in 7 days


Well... close enough :-) Roger apparently isn't printing dates, while
Zed isn't indenting. Mark decided that a month is a month, and it needs
no stinking zeroes :-)

Did you notice something else funny? Yep, so did I... Dave's solution is
not only allowing for the date of yesterday, but also for one day more
then the specified range... I think those "proximity strings" are not
right, either...


Now that we've taken care of that, here are some other things:


* Recursion: it wasn't asked for, but Zed's solution implements this
quite well, via File::Find.

* -n switch: Every solution dealt with this. No problem.

* "2nd something of every something" format:

Roger tried this. I made a test with this:

1th thursday of every month: something happens

It worked, but gave me a warning... I wrote this and discovered that I
was being stupid :-) Then, I used this:

1st thursday of every month: something happens

It worked. No warnings :-)

* Speed: I had no time to test for speed... Roger started his entry by
stating that Date::Manip is slow, while Dave stated his code would run
pretty fast... that's all I have to say for now...

* Others: Dave implemented some other features: letting the user select
the files he wants to be processed, providing an option for specifying
the date format (which I didn't test much) and another for the directory
to process.



Bottom line:

While Roger demonstrated his ability with the "Swiss Army Chainsaw of
date modules", Date::Manip, making available the "2nd Tuesday of every
month" format, Zed made use of File::Find for immediate recursion and
Dave went with POSIX and implemented a lot of nice features. Mark, on
his side, decided to go with Time::Local and actually made it work for
all tested cases... O:-) I believe an even better script could be made
out of these three ones, as they all have strong points.


Final considerations:

By using File::Find, Zed's solution made it hard for me to test it... It
was trying to parse my test file, but also the swap file created by vim,
given that I was editing the file :-) It took me a while to figure that
out (the time for printing some debugging information including the name
of the file being opened, at least).

Testing your code was very fun, and something I hope to do again in a
near future :-) Thanks for your time, guys :-)

jac

[ My thanks also to anyone who worked on the quiz and didn't send in a
  solution.  

  I have a bit of a problem now.  I sent out an 'expert' quiz, the
  word ladder one, thinking that I would write up the report about it
  myself, since I am on leave this week.  I had not expected it to
  turn out to be one of the most popular quizzes ever, and now I don't
  think I have time to write it up.  I would be grateful if someone
  else would volunteer to do it.  Is anyone interested in looking over
  and testing the many submitted solutions for this quiz?  If so, drop
  me a note at mjd at plover.com.  I will be happy to offer asistance and
  guidance.  Thanks, - MJD ]


  




More information about the NewOrleans-pm mailing list