Homework 6, Problem 1: Virtual Art
[40 points; individual or pair]

 

a.k.a. building a Date class...

Submission: Submit your hw6pr1.py file to Canvas

This week's lab will guide you through creating a class named Date from which you will be able to create objects of type Date. Your objects will have the ability to find the day of the week they fell on (or will fall on), hence, virtual Art!

Be sure to add a docstring to each of the methods you write! The term method refers to a function that is a member of a user-defined class.

The Date class

This is the first EECS 110 assignment in which you will be creating your own datatype, a class named Date.

Starting up...

First, here is the start of the Date class, discussed in class:

 

class Date:

""" a user-defined data structure that

stores and manipulates dates

"""

 

def __init__(self, month, day, year):

""" the constructor for objects of type Date """

self.month = month

self.day = day

self.year = year

 

 

def __repr__(self):

""" This method returns a string representation for the

object of type Date that calls it (named self).

 

** Note that this _can_ be called explicitly, but

it more often is used implicitly via the print

statement or simply by expressing self's value.

"""

s =  "%02d/%02d/%04d" % (self.month, self.day, self.year)

return s

 

 

def isLeapYear(self):

""" Returns True if the calling object is

in a leap year; False otherwise. """

if self.year % 400 == 0: return True

elif self.year % 100 == 0: return False

elif self.year % 4 == 0: return True

return False

 

You should see that in this Date class there are three data members:

In addition, there is a constructor, always named __init__, with two underscore characters on either side of the name. The __repr__ method is named similarly.

Perhaps not surprisingly, the constructor is a function that constructs objects of type Date.

The __repr__ method is used when Python has to represent an object of type Date.

We've included the isLeapYear function for good measure.

Note on "method":
Traditionally, functions called by objects are called methods. There is no really good reason for this. They are functions—the only thing special about them is that they are defined in a class and they are called after a dot or period following the name of an object. For example, you might try these:

>>> d = Date(11, 11, 2008)

>>> d.isLeapYear()

True

 

>>> d2 = Date(3, 15, 2009)

>>> d2.isLeapYear()

False

 

>>> Date(1, 1, 1900).isLeapYear() # no variable needed!

False

 

What's up with self?
One odd thing about the above example is that three different objects of type Date are calling the same isLeapYear code. How does the isLeapYear method tell the different objects apart?

The method does not know the name of the variable that calls it! In fact, in the third example, there is no variable name! The answer is self. The self variable holds the object that calls the method, including all of its data members.

This is why self is always the first input to all of the methods in the Date class (and in any class that you define!): because self is how the method can access the individual data members in the object that called it.

*Please notice also*—this means that a method always has at least one input argument, namely self. However, this value is passed in implicitly when the method is called. For example, isLeapYear is invoked in the example above as Date(1,1,1900).isLeapYear(), and Python automatically passed self, in this case the object Date(1,1,1900), as the first input to the isLeapYear method.

Testing your initial Date class:

Just to get a feel for how to test your new datatype, try out the following calls:

# create an object named d with the constructor

>>> d = Date(11, 11, 2008)

 

# show d's value

>>> d

11/11/2008

 

# a printing example

>>> print('Tuesday is', d)

Tuesday is 11/11/2008

 

# create another object named d2

>>> d2 = Date(11, 11, 2008)

 

# show its value

>>> d2

11/11/2008

 

# are they the same?

>>> d == d2

False

 

# look at their memory locations

>>> id(d)  # return memory address

413488  # your result will be different...

 

>>> id(d2) # again...

430408  # this should differ from above!

 

# yet another object of type Date

>>> d3 = Date(1, 1, 2008)

 

# check if d3 is in a leap year

>>> d3.isLeapYear()

True

 

 

# check if d2 is in a leap year

>>> d2.isLeapYear()

True

 

copy and equals


For this part, you should paste the following two methods (code provided) into your Date class and then test them. We are providing the code so that you have several more examples of what it is like to define functions inside a class.

def copy(self):

""" Returns a new object with the same month, day, year

as the calling object (self).

"""

dnew = Date(self.month, self.day, self.year)

return dnew

 

This method returns a newly constructed object of type Date with the same month, day, and year that the calling object has. Remember that the calling object is named self, so the calling object's month is self.month, the calling object's day is self.day, and so on.

Since you want to create a newly constructed object, you need to call the constructor! This is what you see happening in the copy method.

Try out these examples:

>>> d = Date(1, 1, 2008)

>>> d2 = d.copy()

>>> d

01/01/2008

>>> d2

01/01/2008

 

>>> id(d)

430568  # your memory address may differ

>>> id(d2)

413488  # but this should be different still!

>>> d == d2

False # make sure this is False!

 

def equals(self, d2):

""" Decides if self and d2 represent the same calendar date,

whether or not they are the in the same place in memory.

"""

return self.year == d2.year and self.month == d2.month and self.day == d2.day

 

This method should return True if the calling object (named self) and the input (named d2) represent the same calendar date. If they do not represent the same calendar date, this method should return False. The examples above show that the same calendar date may be represented at multiple locations in memory—in that case the == operator returns False. This method can be used to see if two objects represent the same calendar date, regardless of whether they are at the same location in memory.

Try these examples (after reloading hw6pr1) to get the hang of how this .equals method works:

>>> d = Date(1, 1, 2008)

>>> d2 = d.copy()

>>> d

01/01/2008

>>> d2

01/01/2008

>>> d == d2

False # this should be False!

 

>>> d.equals(d2)

True  # but this should be True!

 

>>> d.equals(Date(1, 1, 2008)) # no name needed!

True

 

>>> d == Date(1, 1, 2008) # tests memory addresses

False # so it _should_ be False

 

Now, the next part of the lab asks you to implement a few methods for the Date class from scratch.

tomorrow First, add the following method to your Date class (you may have notes that help):

Testing tomorrow To test your tomorrow method, you should try several test cases of your own design. Here are a couple of randomly chosen ones to get you started:

>>> d = Date(12, 31, 2008)

>>> d

12/31/2008

>>> d.tomorrow()

>>> d

01/01/2009

 

>>> d = Date(2, 28, 2008)

>>> d.tomorrow()

>>> d

02/29/2008

>>> d.tomorrow()

>>> d

03/01/2008

 

yesterday Next, add the following method to your Date class:

Testing yesterday To test your yesterday method, you should try several test cases of your own design. Here are the reverse of the previous tests:

>>> d = Date(1, 1, 2009)

>>> d

1/1/2009

>>> d.yesterday()

>>> d

12/31/2008

 

>>> d = Date(3, 1, 2008)

>>> d.yesterday()

>>> d

02/29/2008

>>> d.yesterday()

>>> d

02/28/2008

 

addNDays Next, add the following method to your Date class:

In addition, this method should print all of the dates from the starting date to the finishing date, inclusive of both endpoints. Remember that the line print(self) can be used to print an object from within one of that object's methods! See below for examples of the output.

Testing To test your addNDays method, you should try several test cases of your own design. Here are a couple to start with --

>>> d = Date(11, 11, 2008)

>>> d.addNDays(3)

11/11/2008

11/12/2008

11/13/2008

11/14/2008

>>> d

11/14/2008

 

>>> d = Date(11, 27, 2007)

>>> d.addNDays(1265)

11/27/2007

11/28/2007

... lots of dates skipped ...

5/14/2011

5/15/2011

>>> d

5/15/2011

 

subNDays Next, include the following method in your Date class:

In addition, this method should print all of the dates from the starting date to the finishing date, inclusive of both endpoints. Again, this mirrors the addNDays method. See below for examples of the output.

Testing subNDays: you might reverse the above test cases!

 

isBefore Next, add the following method to your Date class:

It might be worth mentioning that you could pass a string or a float or an integer as the input to this isBefore method. In this case, your code will likely raise a TypeError. We won't do this to your code! Python relies on the user to keep track of the types being used. (Java, on the other hand)

Testing isBefore To test your isBefore method, you should try several test cases of your own design. Here are a few to get you started:

>>> d = Date(11, 11, 2008)

>>> d2 = Date(12, 1, 2008)

>>> d.isBefore(d2)

True

>>> d2.isBefore(d)

False

>>> d.isBefore(d)

False

 

isAfter Next, add the following method to your Date class:

You can emulate your isBefore code here OR you might consider how to use the isBefore and equals methods to write isAfter.

Testing isAfter To test your isAfter method, you should try several test cases of your own design. For example, you might reverse the examples shown above for isBefore.

diff Next, add the following method to your Date class:

self - d2

One crucial point: this method should NOT change self NOR should it change d2! You may want to manipulate copies, so that the originals remain unchanged.

Also, The sign of the return value is important! Consider these three cases:

Two approaches not to use!

Testing diff To test your diff method, you should try several test cases of your own design. Here are two relatively short ones:

>>> d = Date(11, 11, 2008)

>>> d2 = Date(12, 20, 2008)

>>> d2.diff(d)

39

>>> d.diff(d2)

-39

>>> d

11/11/2008

>>> d2    # make sure they did not change...

12/20/2008

 

>>> d3 = Date(5, 15, 2011)

>>> d3.diff(d)

915

 

And here are two relatively distant pairs of dates:

>>> d = Date(11, 11, 2008)

>>> d.diff(Date(1, 1, 1899))

40126

>>> d.diff(Date(1, 1, 2101))

-33653

 

You can check other differences at www.timeanddate.com/date/duration.html .

dow Next, add the following method to your Date class:

Hint: How might it help to find the diff from a known date, like Tuesday, November 11, 2008? How might the mod (%) operator help?

Testing dow To test your dow method, you should try several test cases of your own design. Here are a few to get you started:

>>> d = Date(12, 7, 1941)

>>> d.dow()

'Sunday'

 

>>> Date(10, 28, 1929).dow()     # dow is appropriate !

'Monday'

 

>>> Date(10, 19, 1987).dow()     # ditto!

'Monday'

 

>>> d = Date(1, 1, 2100)

>>> d.dow()

'Friday'

 

You can check your days of the week at www.timeanddate.com/calendar/ by entering the year you're interested in.


Computing Calendar Statistics

To finish the lab, we will put your Date class to use! This will include an investigation of your birthday's most likely day-of-the-week, as well as our calendar's statistics for the 13th of each month.

Try the following code and then answer the three questions below.

Consider the following function-- you will want to paste it at the bottom of your hw6pr1.py file. Be sure to paste it OUTSIDE the Date class. That is, make sure this function is indented all the way to the left (not one indentation rightward, as are all of the methods of the Date class)!

def nycounter():

"""Looking ahead to 100 years of celebrations..."""

dowd = {} # dowd == 'day of week dictionary'

dowd["Sunday"] = 0  # a 0 entry for Sunday

dowd["Monday"] = 0  # and so on

dowd["Tuesday"] = 0

dowd["Wednesday"] = 0

dowd["Thursday"] = 0

dowd["Friday"] = 0

dowd["Saturday"] = 0

 

# live for another 100 years:

for year in range(2008, 2108):

d = Date(1, 1, year)  # get ny

print('Current date is', d)

s = d.dow() # get day of week

dowd[s] += 1       # count it

 

print('totals are', dowd)

 

# we could return dowd here

# but we don't need to right now

# return dowd

 

First, try this nycounter function out:

>>> nycounter()

 

Question 1
In a comment above this
nycounter function in your hw6pr1.py file, write one or two sentences describing what this nycounter computes.

Question 2
Based on the
nycounter example, write a function that will compute the same information for your next 101 birthdays. Include the results in a comment below your function.

Question 3
Based on these two examples, write a function that will compute the same information for the 14th of every month for the next 400 years. Since our current calendar cycles every 400 years, your results will be the overall frequency distributions for the 14th of the month for as long as people retain our current calendar system. What day of the week is the 14th most and least likely to fall on? Is it a tie?

Note that you will be taking full advantage of your computer's processor for this problem. You might want to print out a status line, e.g., each year, so that you know that it's making progress. Even so, this will be too slow! So after you've watched the dates slow down and get tired of waiting, read the instructions below and make the indicated changes to speed things up (Control-C will kill your program so you don't have to wait for it to finish).

The problem is with dow -- it's checking longer and longer intervals. How might you change how dow works to speed up the processing in this case?

One way to do it would be to write an alternative method in your Date class, named dow2(self, refDate). The second input to dow2 would be any reference date of the appropriate day of the week. You would call

d.dow2( refDate )

instead of d.dow().

The idea is this: create a variable called refDate at the top of your "thirteenthcounter" function. Initially give that variable the value of the reference date used in your original dow function. However, as you seek into the future, check each thirteenth to see if it's the same day of the week as your original refDate. If it is the same day of the week as the original refDate, then update the value of the refDate variable to be that new date -- namely, the thirteenth of the month you just found.

That way, you will never be calling dow2 on spans of dates of more than a year -- it should finish in less than a minute. You'll notice that the thirteenth is a Friday more often than anything else!

 

If you have gotten to this point, you have completed Lab6! You should submit your hw6pr1.py file at Canvas .

Next

hw6pr2

Lab 6

Homework 6