Beefy Boxes and Bandwidth Generously Provided by pair Networks
"be consistent"
 
PerlMonks  

How preparing the weekly shift roster led to a fascinating discovery...

by McDarren (Abbot)
on Dec 14, 2007 at 14:39 UTC ( [id://657042]=perlmeditation: print w/replies, xml ) Need Help??

Greetings fellow monks :)

I lead a small team of people that work a 24/7 shift roster. One of my less exciting duties is to maintain the roster and provide weekly updates. Whilst I was preparing this weeks update (which I do each Friday), I noticed something interesting which eventually led to quite a fascinating (for me, at least) discovery.

Each week, as I update the roster I save a copy, giving it a filename indicating the day/date. This week, as I was saving the file I noticed that in 2007, Friday has occurred on every day of the month. ie. (1 .. 31).

So this got me to wondering...

  1. Was this true of every day of the week?
  2. How does this vary from year to year, and is there any pattern involved?
So naturally, I wrote some code to find out :)

The result I got was quite surprising, and at first I thought there must be a silly bug in my code that was giving an erroneous result. But I've done some manual (Eyeball MK-II) checking, which does appear to verify it. Although, I am still half-expecting somebody to point out an error and make me look _really_ silly :)
In early iterations of the code I wrote, I was just testing a single year at a time. But as I kept getting similar results no matter which year I gave it, I eventually decided to test all years from 1 .. 2500.

The code I used is given below, but before I get to that - a couple of things to ponder....

  1. Without running the code or writing your own, can you predict the result?
  2. How would you have written this code? (golfers, please step forward)
#!/usr/bin/perl -l use strict; use warnings; use Date::Calc qw/Days_in_Month Day_of_Week/; my @days_of_week = qw/Mon Tue Wed Thu Fri Sat Sun/; for my $year (1 .. 2500) { my %days_of_month; for my $month_of_year(1 .. 12) { for my $day_of_month(1 .. Days_in_Month($year,$month_of_year)) + { my $dow = Day_of_Week($year,$month_of_year,$day_of_month); # Day_of_Week uses a 1-based index, hence the --$dow $days_of_month{$day_of_month}{$days_of_week[--$dow]}++; } } for my $day_of_month (sort {$a <=> $b} keys %days_of_month) { next if scalar keys %{$days_of_month{$day_of_month}} == 7; my @missing_days; for (@days_of_week) { push (@missing_days, $_) if !defined $days_of_month{$day_o +f_month}{$_}; } print "In $year, Day $day_of_month does not fall on ", join(", ", @missing_days); } }

Note that I deliberately haven't described the result, as I think that would spoil the fun. To find out, you'll either have to run the above code, or write your own :p

Cheers,
Darren :)

  • Comment on How preparing the weekly shift roster led to a fascinating discovery...
  • Download Code

Replies are listed 'Best First'.
Re: How preparing the weekly shift roster led to a fascinating discovery...
by talexb (Chancellor) on Dec 14, 2007 at 16:06 UTC

    Regarding testing years 1 to 2500, I'm not sure it's absolutely necessary to go through that many iterations. There are only 14 possible calendars anyway: the year can start on one of seven weekdays, and it's either a leap year or it isn't. Seven times two is fourteen different calendars.

    Alex / talexb / Toronto

    "Groklaw is the open-source mentality applied to legal research" ~ Linus Torvalds

      heh, yes... very good point. I've always been amused (and often quite frustrated) at my own ability to overlook the most obvious logic when my mind becomes focussed on one particular aspect of a problem. Something to do with trees and forests, I think :)

      However, I should point out that in order to see the full pattern that emerges it is necessary to test for ~400 years. This makes sense of course, given the (Gregorian Calendar) Leap Year Rules.

      The output from the following (modified from my original) code gives an indication of the pattern:

      #!/usr/bin/perl -l use strict; use warnings; use Date::Calc qw/Days_in_Month Day_of_Week/; my @days_of_week = qw/Mon Tue Wed Thu Fri Sat Sun/; my $cnt; my @cnt; for my $year (1 .. 2500) { my %days_of_month; for my $month_of_year(1 .. 12) { for my $day_of_month(1 .. Days_in_Month($year,$month_of_year)) + { my $dow = Day_of_Week($year,$month_of_year,$day_of_month); $days_of_month{$day_of_month}{$days_of_week[--$dow]}++; } } for my $day_of_month (sort {$a <=> $b} keys %days_of_month) { next if scalar keys %{$days_of_month{$day_of_month}} == 7; my @missing_days; for (@days_of_week) { push (@missing_days, $_) if !defined $days_of_month{$day_o +f_month}{$_}; } $cnt++; if ($missing_days[0] eq 'Sun') { push @cnt, $cnt; if ($cnt == 11) { print "$year:@cnt"; @cnt = (); } $cnt = 0; } } }

      However, it's not so much the pattern that I found interesting - but the fact that every year it's _always_ the same day of the month (31st) that doesn't occur on every day of the week.

      Cheers,
      Darren :)

        It might be related to the fact that there are only seven 31s in a year compared to 11 for 29 and 30 and 12 for all lower numbers :-) (I originaly thought that it's because there are only 6 31s, but that was before I counted them.) In either case it's easier to miss one of seven days if you only have seven attempts than if you have 11 or 12.

      I guess you could:
      #!/usr/bin/perl use strict; use warnings; use Date::Calc qw/Days_in_Month Day_of_Week/; my @days_of_week = qw/NULL Mon Tue Wed Thu Fri Sat Sun/; my $i=0; # Our 14 calendars are in these years, this century # see: http://www.koshko.com/calendar/calendar-lookup-gregorian.shtml for (6, 7, 1, 2, 3, 10, 11, 12, 24, 8, 20, 4, 16, 0) { my $year=$_+2000; $i++; my %days_of_month; for my $month_of_year(1 .. 12) { my $sday = Day_of_Week($year,$month_of_year,1); # only need to cal +l this once for my $day_of_month(1 .. Days_in_Month($year,$month_of_year)) { $sday = 1 if($sday>7); my $dow = $sday++; $days_of_month{$day_of_month}{$dow}++; } } for my $day_of_month (sort keys %days_of_month) { next if(scalar keys %{$days_of_month{$day_of_month}} == 7); my @missing_days; for (1..7) { push (@missing_days, $days_of_week[$_]) if !defined $days_of_mon +th{$day_of_month}{$_}; } print "Calendar $i: In $year, Day $day_of_month does not fall on " +, join(", ", @missing_days), "\n"; } }
      --
      Linux, perl, punk rock, cider: charlieharvey.org.uk.
Re: How preparing the weekly shift roster led to a fascinating discovery...
by CountZero (Bishop) on Dec 14, 2007 at 20:07 UTC
    Sometimes we run away with ourselves ...

    You don't have to test all years, nor 400 years, not even 14 years or 7!

    Considering just one year (any year you wish) is enough.

    This might seem surprising, but it really isn't. As long as you don't start your year on the 1st of January, but on the 1st of March.

    Suppose, the 1st of March is a Monday then:

    • 31st March is a Wednesday (30 days further, so 30 mod 7 = 2; 1 + 2 = 3 => Wednesday)
    • 31st of May is a Monday (91 days after 1st of March: 91 mod 7 = 0; 1 + 0 = 1 => Monday)
    • 31st of July is a Saturday (152 days after 1st of March; 152 mod 7 = 5)
    • 31st of August is a Tuesday (183 days after 1st of March, 183 mod 7 = 1)
    • 31st of October is a Sunday (244 days after 1st of March, 244 mod 7 = 6)
    • 31st of December is a Friday (305 days since 1st of March, 305 mod 7 = 4)
    • 31st of January is a Monday (336 since 1st of March of the previous year, 336 mod 7 = 0)
    As you can see there is never a "+3" in the above sequence, meaning that the 31st will miss "day + 3" (where day 1 is the day of 1st of March).

    Because of leap years the last day of January can at the most shift one day, but as it is on the "+0" spot, it can never make it to the "+3" spot, hence "day +3" is always missing for the 31st.

    QED

    CountZero

    A program should be light and agile, its subroutines connected like a string of pearls. The spirit and intent of the program should be retained throughout. There should be neither too little or too much, neither needless loops nor useless variables, neither lack of structure nor overwhelming rigidity." - The Tao of Programming, 4.1 - Geoffrey James

      Very nice++.

      But there is a little thinko in there. Because Jan 31 next year is a +0 day next year does not mean it is this year. For a non leap year it is +6 and +5 for a leap year.

        Yes, but it doesn't matter for the problem at hand. January of the previous year can at most be one day off its normal, non-leap day, but it can never become a "+3".

        To explain in a bit more detail what you have already found: As a normal year has 52 weeks and one day, January of the same year must thus be a "-1" (or "+6") compared to the day of the 1st of March of the same year.

        In a leap year, (52 weeks and two days) January 31st will be a "-2" (or "+5") day, but it can never ever be a "+3" day.

        CountZero

        A program should be light and agile, its subroutines connected like a string of pearls. The spirit and intent of the program should be retained throughout. There should be neither too little or too much, neither needless loops nor useless variables, neither lack of structure nor overwhelming rigidity." - The Tao of Programming, 4.1 - Geoffrey James

      mmm, yes.. that is a nice bit of deductive reasoning, but...

      What if you hadn't of know beforehand that the 31st was the _only_ one that missed out?

      I mean, once glide let the cat out of the bag and the result was obvious, then it becomes much easier to apply a bit of backwards logic to explain the phenomena.

      Of course, I didn't know what the result would look like until I'd written some code to test it. And once the code was written, it became a simple matter to modify it to test for as many years as I wished.

      By the way..

      Because of leap years the last day of January ..
      Don't you mean February?

      Cheers,
      Darren :)

        What if you hadn't of know beforehand that the 31st was the _only_ one that missed out?
        Then I would never have given the matter a single thought.

        But ... consider this: someone finds a curious phenomenon and reports it to his fellow scientists. These start running a number of experiments which prove or disprove this phenomenon in a variety of circumstances. Then someone steps in and formulates a theory and finally someone proves (based upon the existing body of scientific knowledge) that the theory is correct.

        I think we just have witnessed the scientific method at work in our Monastery!

        Don't you mean February?
        No no, I really mean January. February never has 31 days, so by starting my "year" in March (as did the old Romans -- why do you think September really means the seventh month?) my reasoning does not have to take into account leap years but for the month of January (which as lidden said can only be a "+5" of "+6" day but never a "+3".) thus making my proof much easier and clearer.

        Shall we --in line with good scientific practice and hubris-- call this theory the "McDarren-CountZero-lidden Theory of the Missing Weekday of the 31st"?

        CountZero

        A program should be light and agile, its subroutines connected like a string of pearls. The spirit and intent of the program should be retained throughout. There should be neither too little or too much, neither needless loops nor useless variables, neither lack of structure nor overwhelming rigidity." - The Tao of Programming, 4.1 - Geoffrey James

        If you restrict to months different from february, all of them have at least 30 days and you can easily see that "having" days 1..7 always gives you all the days up to 30 (just write numbers 1..30 putting a newline every 7 of them). Restricting to months from march to december (which always present the same pattern, whatever year you consider after the transition from Julian to Gregorian calendars) you can quickly check that all days 1..7 are there for all days of week (it suffices to see it for only one of them, then that's only a matter of modulo-7 maths).

        So the only one that remains tricky is 31, because not all months "support" it.

        Flavio
        perl -ple'$_=reverse' <<<ti.xittelop@oivalf

        Io ho capito... ma tu che hai detto?
Re: How preparing the weekly shift roster led to a fascinating discovery...
by glide (Pilgrim) on Dec 14, 2007 at 15:20 UTC
    and the result is ... (only a small part)
    In 2455, Day 31 does not fall on Thu In 2456, Day 31 does not fall on Sat In 2457, Day 31 does not fall on Sun In 2458, Day 31 does not fall on Mon In 2459, Day 31 does not fall on Tue In 2460, Day 31 does not fall on Thu In 2461, Day 31 does not fall on Fri In 2462, Day 31 does not fall on Sat In 2463, Day 31 does not fall on Sun In 2464, Day 31 does not fall on Tue In 2465, Day 31 does not fall on Wed In 2466, Day 31 does not fall on Thu In 2467, Day 31 does not fall on Fri In 2468, Day 31 does not fall on Sun In 2469, Day 31 does not fall on Mon In 2470, Day 31 does not fall on Tue

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://657042]
Approved by talexb
Front-paged by tye
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others romping around the Monastery: (6)
As of 2024-04-23 18:51 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found