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.
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:
tomorrow
method, this method should not return
anything. Rather, it should change the calling object so
that it represents N calendar days after
the date it originally represented. How might you use tomorrow to implement
this? 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.
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