Exercise: Calculate # work days between two dates
[1/3] from: edoconnor::gmail::com at: 31-Jan-2007 16:22
I found Sununda's algorithm challenge to be very interesting. I'm often amazed by the different approaches to a given problem. In this spirit, I thought I'd offer up a basic exercise based on a post about Ruby I came across yesterday. See: *Calculate the number of working days between two dates* http://www.railsonwave.com/railsonwave/2007/1/30/calculate-the-number-of-working-days-between-two-dates Here's an approach I took, which embellishes with a refinement: calc-workdays: func [ "Return number of workdays between two dates, excluding holidays" date1 [date!] "Start date" date2 [date!] "End date" holidays [block!] "Block of dates to exclude (holidays, etc.)" /non "Return number of non-work days (weekend + holidays) between 2 dates" /local days day1 day2 diff param ][ days: copy  set [day1 day2] sort reduce [date1 date2] diff: day2 - day1 param: pick [[> 5 union][< 6 exclude]] either found? non  loop diff [ day1: day1 + 1 if do param/1 day1/weekday param/2 [append days day1] ] return length? do param/3 days holidays ] Results:
>> calc-workdays now/date 3-feb-2007 [1-feb-2007]
>> print calc-workdays/non now/date 3-feb-2007 [1-feb-2007]
>> calc-workdays now/date 31-dec-2007 [25-dec-2007 1-Jan-2008]
>> print calc-workdays/non now/date 31-dec-2007 [25-dec-2007 1-Jan-2008]
== 98 Anyone care to share a different or more natural approach? One of the things I'd like to see in REBOL's future is a guide or "phrase-book" for common expressions found in problems big and small. Ed
[2/3] from: SunandaDH::aol::com at: 1-Feb-2007 21:15
> I found Sununda's algorithm challenge to be very interesting. I'm often > amazed by the different approaches to a given problem.
Thanks. I was amazed too at the range of responses. Here's my offering for your exercise. My tweaks are: 1. Avoid the sort as we have only two items (I haven't timed that doing it by hand is faster than letting REBOL set up a sort for just two items -- it may be worth someone running a benchmark on that one day, 2. Avoid the loop around *all* the days in the period. I reckoned if I could round the first date down to the nearest Monday while rounding the second date up to the next nearest Monday, I was half way there. The tricky part was tracking how many countable days I'd added in by the rounding -- hence the blocks of obscure negative numbers. This theoretically makes it faster -- especially for very wide date ranges. 3. That left me needing to run a loop against the extra holiday days, checking each if was in range and not a weekend. And I had to do that before Step 2 so it was on the original dates, not the rounded ones. This may counteract the theoretical speed improvements in Step 2, especially if there are many holiday dates for a narrow date range. calc-workdays: func [ date1 [date!] date2 [date!] holidays [block!] /non /local dates offset hol-count working-days ][ ;; Put dates in order ;; ------------------ dates: copy reduce [date1] either date1 < date2 [append dates date2] [insert dates date2] ;; count holidays in range ;; ----------------------- hol-count: 0 foreach hd holidays [ if all [ hd/weekday <= 5 ;; got to be a weekday to count hd >= dates/1 hd <= dates/2][ hol-count: hol-count + 1 ] ] ;; Round dates to Mondays ;; ---------------------- offset: 0 offset: offset + pick [0 -1 -2 -3 -4 -4 -4] dates/1/weekday dates/1: dates/1 + 1 - dates/1/weekday offset: offset + pick [-5 -4 -3 -2 -1 -1 -1] dates/2/weekday dates/2: dates/2 + 8 - dates/2/weekday ;; Calculate the working / non-working days ;; -------------------------------- working-days: (dates/2 - dates/1) / 7 * 5 + offset - hol-count if non [return (abs (date1 - date2)) - working-days] return working-days ] That was fun -- thanks! Sunanda.
[3/3] from: edoconnor::gmail::com at: 2-Feb-2007 16:22
Hi Sunuda-- Thanks for your code & explanation. A few comments:
>> 1. Avoid the sort as we have only two items...
I was attempting to adhere to the philosophy of being liberal in what arguments I accept, i.e., accept two dates without requiring a particular order. I see you did the same, but avoided the 'sort. Looking back to my function, I could probably just accept the dates and use the 'abs function to coerce a positive result: diff: abs day2 - day1 which I see you do in the final lines of your function.
>> 2, 3...
Your solution here is quite creative; its not an approach I would have considered! Since I consider myself to be an intermediate REBOL programmer, I find it highly instructive to see solutions expressed at different skill levels: 1. The quick-and-dirty "most direct" solution. 2. A version that has been cleaned-up (for ease of maintenance) and error checking. 3. A version that has been optimized for speed/scalability or GUI/CGI. Eg. The difference between: 1 & 2 Generate a list of files and mp3's from a website or RSS feed.( http://www.rebol.com/docs/quick-start5.html for level 2) 3 Adding download support for large media files such as podcasts. (using code such as: http://www.rebol.net/cgi-bin/blog.r?view=0281) Thanks for your code. Ed On 2/1/07, SunandaDH-aol.com <SunandaDH-aol.com> wrote: