The Doomsday algorithm, devised by mathematician J. H. Conway, computes the day of the week any given date fell on. The algorithm is designed to be simple enough to memorize and use for mental calculation.
Example. With the algorithm, we can compute that July 4, 1776 (the day the United States declared independence from Great Britain) was a Thursday.
The algorithm is based on the fact that for any year, several dates always fall on the same day of the week, called the doomsday for the year. These dates include 4/4, 6/6, 8/8, 10/10, and 12/12.
Example. The doomsday for 2016 is Monday, so in 2016 the dates above all fell on Mondays. The doomsday for 2017 is Tuesday, so in 2017 the dates above will all fall on Tuesdays.
The doomsday algorithm has three major steps:
Each step is explained in detail below.
The doomsday for the first year in a century is called the anchor day for that century. The anchor day is needed to compute the doomsday for any other year in that century. The anchor day for a century $c$ can be computed with the formula: $$ a = \bigl( 5 (c \bmod 4) + 2 \bigr) \bmod 7 $$ The result $a$ corresponds to a day of the week, starting with $0$ for Sunday and ending with $6$ for Saturday.
Note. The modulo operation $(x \bmod y)$ finds the remainder after dividing $x$ by $y$. For instance, $12 \bmod 3 = 0$ since the remainder after dividing $12$ by $3$ is $0$. Similarly, $11 \bmod 7 = 4$, since the remainder after dividing $11$ by $7$ is $4$.
Example. Suppose the target year is 1954, so the century is $c = 19$. Plugging this into the formula gives $$a = \bigl( 5 (19 \bmod 4) + 2 \bigr) \bmod 7 = \bigl( 5(3) + 2 \bigr) \bmod 7 = 3.$$ In other words, the anchor day for 1900-1999 is Wednesday, which is also the doomsday for 1900.
Exercise 1.1. Write a function that accepts a year as input and computes the anchor day for that year's century. The modulo operator %
and functions in the math
module may be useful. Document your function with a docstring and test your function for a few different years. Do this in a new cell below this one.
def anchor(x):
c = str(x)[0:2] # 0:1 only means the 1st character
c = int(c)
a = (5*(c%4)+2)%7
return a
anchor(1987) #test
x = 1987
print x%
Once the anchor day is known, let $y$ be the last two digits of the target year. Then the doomsday for the target year can be computed with the formula: $$d = \left(y + \left\lfloor\frac{y}{4}\right\rfloor + a\right) \bmod 7$$ The result $d$ corresponds to a day of the week.
Note. The floor operation $\lfloor x \rfloor$ rounds $x$ down to the nearest integer. For instance, $\lfloor 3.1 \rfloor = 3$ and $\lfloor 3.8 \rfloor = 3$.
Example. Again suppose the target year is 1954. Then the anchor day is $a = 3$, and $y = 54$, so the formula gives $$ d = \left(54 + \left\lfloor\frac{54}{4}\right\rfloor + 3\right) \bmod 7 = (54 + 13 + 3) \bmod 7 = 0. $$ Thus the doomsday for 1954 is Sunday.
Exercise 1.2. Write a function that accepts a year as input and computes the doomsday for that year. Your function may need to call the function you wrote in exercise 1.1. Make sure to document and test your function.
def doomsday(x):
y = str(x)
y = int(y[2:]) # the last two digits
d = (y+int(y/4)+anchor(x))%7
return d
#test
doomsday(1954)
The final step in the Doomsday algorithm is to count the number of days between the target date and a nearby doomsday, modulo 7. This gives the day of the week.
Every month has at least one doomsday:
Example. Suppose we want to find the day of the week for 7/21/1954. The doomsday for 1954 is Sunday, and a nearby doomsday is 7/11. There are 10 days in July between 7/11 and 7/21. Since $10 \bmod 7 = 3$, the date 7/21/1954 falls 3 days after a Sunday, on a Wednesday.
Exercise 1.3. Write a function to determine the day of the week for a given day, month, and year. Be careful of leap years! Your function should return a string such as "Thursday" rather than a number. As usual, document and test your code.
date_list_reg = {1:10,2:28,3:21,4:4,5:9,6:6,7:11,8:8,9:5,10:10,11:7,12:12}
# leap year
date_list_leap = {1:11,2:29,3:21,4:4,5:9,6:6,7:11,8:8,9:5,10:10,11:7,12:12}
day_of_week = {1:"Monday",2:"Tuesday",3:"Wednesday",4:"Thursday",5:"Friday",6:"Saturday",0:"Sunday"}
import calendar
def dayofweek(d,m,y): # d: day; m: month; y:year
for i in range(1,13):
if m==i:
#determine if it is a leap year
if calendar.isleap(y):
ed = d-date_list_leap[m] # no need to use abs() because the d value maybe smaller
else :
ed = d-date_list_reg[m]
ed = ed%7 # the difference of the day of week between doomsday
ed = ed + doomsday(y) # this value maybe larger than 7
ed = ed%7 # the day of the week
return day_of_week[ed]
# test
dayofweek(21,7,2016)
Exercise 1.4. How many times did Friday the 13th occur in the years 1900-1999? Does this number seem to be similar to other centuries?
count = 0
for y in range(1900,2000):
for m in range(1,13):
if dayofweek(13,m,y) == "Friday":
count = count+1
print count
This number is similar to other centuries
Exercise 1.5. How many times did Friday the 13th occur between the year 2000 and today?
count = 0
for y in range(2000,2017): # from 2000 to 2016
for m in range(1,13):
if dayofweek(13,m,y) == "Friday":
count = count+1
print count
# today is 01/21/2017, we know 13th is Friday
dayofweek(13,1,2017)
Thus, there were 30 times for 13th to be Friday.
Exercise 2.1. The file birthdays.txt
contains the number of births in the United States for each day in 1978. Inspect the file to determine the format. Note that columns are separated by the tab character, which can be entered in Python as \t
. Write a function that uses iterators and list comprehensions with the string methods split()
and strip()
to convert each line of data to the list format
[month, day, year, count]
The elements of this list should be integers, not strings. The function read_birthdays
provided below will help you load the file.
def read_birthdays(file_path):
"""Read the contents of the birthdays file into a string.
Arguments:
file_path (string): The path to the birthdays file.
Returns:
string: The contents of the birthdays file.
"""
with open(file_path) as file:
return file.read()
# read the file as string
birthdays = read_birthdays("C:/Users/Xin~/Documents/63xinxin.github.io/birthdays.txt")
def clean_birthdays(birthdays):
# split by tab
bs = birthdays.split("\t")
# return to a string
bs = "\t".join(bs)
bs = bs.replace("\n","") # get rid of \n in the list content
bs = bs.split("\t")
# adding 1/1/1978 to the list
bs[0] = '1/1/78'
# ==================
# only using loops ?? how to transform this into list comprehensions
# ==================
import calendar
# iteration
ite_last = 0
bs_trans = []
for i in range(1,13):
daysinmonth = calendar.monthrange(1978,i)[1] # number of days in a month
for j in range(1,daysinmonth+1):
ite = j +ite_last
ele = bs[ite-1]
ele = ele.split("/")
ele_trans = [i, j, int("19"+ele[2]),int(ele[0])] # adding character in a string: just using "+"
bs_trans.append(ele_trans)
ite_last = ite_last + daysinmonth
return bs_trans
out = clean_birthdays(birthdays)
out[0:9] # test
Exercise 2.2. Which month had the most births in 1978? Which day of the week had the most births? Which day of the week had the fewest? What conclusions can you draw? You may find the Counter
class in the collections
module useful.
# finding the maximum value for births
#counts = [i[3] for i in bs_trans] the 4th elements in each nested list
#counts.index(max(counts)) the index of maximum value, equals to which.max() in r
#==========================================
# import collections
# bs_trans_c = collections.Counter(bs_trans)
# How to use class Counter here ???
# =========================================
# group by month and add them together, then compare
# find the day of the week for each day and the correspounding births. Group by day of the week and then compare
#import collections as ct
#c = ct.Counter(out)
# make a dictionary key is month: value is births
# how to add multiple elements for one key is the difficult part
month_dict={}
for i in out: # i is every list in out
if i[0] in month_dict: # i[0] is month; also the value of month_dict is just key if I call it directly
month_dict[i[0]].append(i[3]) # elements have common key
else:
month_dict[i[0]] = [i[3]]
# the total amount of birth each month
bs_per_month = [sum(month_dict[i]) for i in month_dict]
bs_per_month
# the month had the most birth
print bs_per_month.index(max(bs_per_month))
print month_dict.keys()
# create a dictionary for the key to be the day if the week
dow_dict={}
for i in out:
# the day of the week
dow = dayofweek(i[1],i[0],i[2])
if dow in dow_dict:
dow_dict[dow].append(i[3])
else:
dow_dict[dow] = [i[3]]
# the total amount of birth each day of week
bs_dow = [sum(dow_dict[i]) for i in dow_dict ]
bs_dow
# the month had the most birth
print bs_dow.index(max(bs_dow))
# the month had the fewest birth
print bs_dow.index(min(bs_dow))
print dow_dict.keys()
In Octorber 1978, the people of births was the largest. Wednesday had the most birth and Monday had the fewest birth
Exercise 2.3. What would be an effective way to present the information in exercise 2.2? You don't need to write any code for this exercise, just discuss what you would do.
I can row bind all the elements from the nested list into a dataframe, then group by month or group by day of the week. After that I can do some calculation to compare which one is the largest.