Problem Set 12
Preliminaries
In your work on this assignment, make sure to abide by the collaboration policies of the course.
Don’t forget to use docstrings and to take whatever other steps are needed to create readable code.
If you have questions while working on this assignment, please come to TA help hours or post them on Piazza.
Make sure to submit your work on Gradescope, following the procedures found at the end of the assignment.
All problems are due by 10 p.m. EDT on Saturday October 14, 2023.
Suggested self-deadline of Friday October 13, 2023.
Important note regarding test cases and Gradescope:
-
You must test each function after you write it. Here are two ways to do so:
- Run your file after you finish a given function in the interactive window, where you can call the function using different inputs and check to see that you obtain the correct outputs.
-
Add test calls to the bottom of your file, inside the
if __name__ == '__main__'
control structure. For example:if __name__ == '__main__': print("mystery(6,7) returned", mystery(6,7))
These tests will be called every time that you run the file, which will save you from having to enter the tests yourself. We have given you an example of one such test in the starter file.
-
You must not leave any
print
statements in the global scope. This will cause an error with the Gradescope autograder. Make sure all of yourprint
statements are inside a function scope or inside theif __name__ == '__main__'
control structure.
Problem 1: Using string methods
20 points; pair-optional or group-of-three-optional
This problem will give you practice with using the methods that are inside every string object.
Begin by downloading this file: ps12pr1.py
.
When you open the file in VS Code, you’ll see that we’ve given you the following strings:
s1 = 'Three little kittens lost their mittens' s2 = 'Star light, star bright'
We have also given you the solution to the first puzzle.
Warmup
Run ps12pr1.py
in VS Code, so that the strings s1
and s2
will be
available to you in the Python Shell.
Next, enter the following method calls and other expressions from the Shell, and take note of the values that are returned:
>>> s1.upper() >>> s1 >>> s2.lower() >>> s2 >>> s2.count('s') >>> s2.lower().count('s') >>> s1.count('tt') >>> s1.split() >>> s1.split('t') >>> s1.upper().split('T') >>> s1.replace('th', 'f') >>> s1.lower().replace('th', 'f') >>> s2.replace('r', 'x') >>> s2.replace('ar', 'amp') >>> s1 >>> s2
Make sure that the result of each method call makes sense, and perform whatever additional calls are needed to ensure that you understand what each of these methods does. You may also want to consult the online documentation for Python’s string class.
The Puzzles
Your task is to add answers to ps12pr1.py
for the remaining puzzles,
following the format that we’ve given you for puzzle 0.
Important
Each expression that you construct must:
- begin with either
s1
ors2
- use one or more string methods
Because our goal is to practice using methods, your expressions may NOT use:
- indexing or slicing (e.g.,
s1[1]
ors2[2:4]
) - any operator (e.g., the
+
operator)
Here are the puzzles:
-
Use
s1
and one or more string methods to count all occurrences of the letter T (both upper-case and lower-case) ins1
, and assign the count to the variableanswer0
. The expected answer is 9. We’ve given you the code for this puzzle. -
Use
s1
and one or more string methods to create the string'Three lipple kippens lost their mippens'
Your answer for this and the remaining puzzles should follow the format that we’ve given you for puzzle 0. In other words, it should look like this:
# Puzzle 1 answer1 =
where you put the appropriate expression to the right of the assignment operator (
=
). Please leave a blank line between puzzles to make things more readable. -
Use
s2
and one or more string methods to create the list['Sta', ' light, sta', ' b', 'ight']
Assign the result to the variable
answer2
. -
Use
s2
and one or more string methods to create the string'NIGHT LIGHT, NIGHT BRIGHT'
Assign the result to the variable
answer3
. -
Use
s1
and one or more string methods to create the list['', 'ree little kittens lost ', 'eir mittens']
Assign the result to the variable
answer4
. -
Use
s2
and one or more string methods to create the list['Star look', ' star brook']
Assign the result to the variable
answer5
.
Problem 2: A Date
class
50 points; pair-optional or group-of-three-optional
Some people have an extraordinary talent to compute (in their heads) the day of the week that any past date fell on. For example, if you tell them that you were born on October 21, 2000, they’ll be able to tell you that you were born on a Saturday!
In this problem, you will create a Date
class, from which you will
be able to create Date
objects that represent a day, month, and
year. You will add functionality to this class that will enable Date
objects to find the day of the week to which they correspond.
Getting started
Begin by downloading the file ps12pr2.py and
opening it in VS Code. We have given you the following methods to start:
-
The
__repr__(self)
method, which returns a string representation of aDate
object. This method will be called when an object of typeDate
is printed. It can also be tested by simply evaluating an object from the Shell. This method formats the month, day, and year that represent aDate
object into a string of the form'mm/dd/yyyy'
and returns it. -
The
is_leap_year(self)
method, which returnsTrue
if the called object is in a leap year, andFalse
otherwise. In other words, when we create aDate
object and call itsis_leap_year
method, the method will return whether that specificDate
object falls in a leap year. -
The
copy(self)
method, which returns a newly-constructed object of typeDate
with the same month, day, and year that the called object has. This allows us to create deep copies ofDate
objects.
Your tasks
Below you will add several new methods to the Date
class. Be sure
to thoroughly test your methods for all cases to ensure that they are
correct. In addition, make sure to include a docstring for each
method that you write.
-
Implement the
Date
constructor – i.e., the__init__
method. We have given you the header for this method, and you should fill in the body so that it initializes the attributes of theDate
object (month
,day
, andyear
) to the values that are passed in as parameters.Don’t forget to use the keyword
self
when specifying an attribute. For example, when initializing themonth
attribute, you should write a statement that looks like this:self.month = ...
Here is some code you can use to test your constructor:
>>> d1 = Date(9, 28, 2020) >>> d1.month 9 >>> d1.day 28 >>> d1.year 2020
Note that we don’t actually use the name
__init__
to call the method. Rather, we call it by using the name of the class (Date
). -
Once you have implemented the constructor, read over the rest of the starter code that we’ve given you. Make sure that you understand how the various methods work.
Try the following interactions in the Python Shell to experiment with the
__repr__
, andis_leap_year
methods:>>> d1 = Date(9, 28, 2020) # An example of using the __repr__ method. Note that no quotes # are displayed, even though the function returns a string. >>> d1 09/28/2020 # Check if d1 is in a leap year -- it is! >>> d1.is_leap_year() True # Create another object named d2 >>> d2 = Date(1, 1, 2021) # Check if d2 is in a leap year. >>> d2.is_leap_year() False
Next, try the following examples in the Python Shell to illustrate why we will need to override the
__eq__
method to change the meaning of the==
operator:>>> d1 = Date(1, 1, 2019) >>> d2 = d1 >>> d3 = d1.copy() # Determine the memory addresses to which the variables refer. >>> id(d1) 430542 # Your memory address may differ. >>> id(d2) 430542 # d2 is a reference to the same Date that d1 references. >>> id(d3) 413488 # d3 is a reference to a different Date in memory. # The == operator tests whether memory addresses are equal. >>> d1 == d2 True # Shallow copy -- d1 and d2 have the same memory address. >>> d1 == d3 False # Deep copy -- d1 and d3 have different memory addresses.
-
Write a method
advance_one(self)
that changes the called object so that it represents one calendar day after the date that it originally represented.Notes:
-
This method should not return anything. Instead, it should change the value of one or more variables inside the called object.
-
Since we are advancing the
Date
object by one day,self.day
will change. Depending on what day it is,self.month
andself.year
may also change. -
You may find it helpful to use the following list by declaring it on the first line of the method:
days_in_month = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
You can then use this list to quickly determine the number of days in a month. For example,
days_in_month[1]
is 31 to represent that January (month 1) has 31 days. You can useself.month
to index this list to find the number of days in the month that is represented by aDate
object.If you use this approach, be sure to take into account that the
days_in_month
list is not accurate forDate
objects that represent February during leap years. However, you can use anif
statement to account for this case when necessary. We showed you this adjustment in lecture.
Examples:
>>> d = Date(12, 31, 2018) >>> d 12/31/2018 >>> d.advance_one() >>> d 01/01/2019 >>> d = Date(2, 28, 2020) >>> d.advance_one() >>> d 02/29/2020 >>> d.advance_one() >>> d 03/01/2020 >>> d.advance_one() >>> d 03/02/2020
-
-
Write a method
advance_n(self, n)
that changes the calling object so that it representsn
calendar days after the date it originally represented. Additionally, the method should print all of the dates from the starting date to the finishing date, inclusive of both endpoints.Notes:
-
This method should not return anything. Instead, it should change the value of one or more variables inside the called object.
-
Don’t copy code from the
advance_one
method. Instead, you should call theadvance_one
method in a loop to accomplish the necessary changes. -
Because the
advance_one
method doesn’t explicitly return a value, it will implicitly return the special valueNone
. As a result, you need to be careful how you call it. In particular, you should not call it as part of an assignment or as part of aprint
statement. For example, the following would not work:# don't do this! print(self.advance_one()) # don't do this! self = self.advance_one()
Rather, you should simply call the method on its own line, and ignore the value of
None
that is returned:self.advance_one()
-
To print the current state of the
Date
object, you can simply do the following:print(self)
since doing so will call the
__repr__
method to produce a string representation ofself
that you can print.-
This method should work for any nonnegative integer
n
. -
If
n
is 0, only the starting date should be printed.
Examples:
>>> d = Date(4, 8, 2019) >>> d.advance_n(3) 04/08/2019 04/09/2019 04/10/2019 04/11/2019 >>> d 04/11/2019 >>> d = Date(4, 8, 2019) >>> d.advance_n(0) 04/08/2019 >>> d 04/08/2019
-
-
Write a method
__eq__(self, other)
that returnsTrue
if the called object (self
) and the argument (other
) represent the same calendar date (i.e., if the have the same values for theirday
,month
, andyear
attributes). Otherwise, this method should returnFalse
.Recall from lecture that the name
__eq__
is a special method name that allows us to override the==
operator–replacing the default version of the operator with our own version. In other words, when the==
operator is used withDate
objects, our new__eq__
method will be invoked!This method will allow us to use the
==
operator to see if twoDate
objects actually represent the same date by testing whether their days, months, and years are the same, instead of testing whether their memory addresses are the same.After implementing your
__eq__
method, try re-executing the following sequence of statements from Task 0:>>> d1 = Date(1, 1, 2019) >>> d2 = d1 >>> d3 = d1.copy() # Determine the memory addresses to which the variables refer. >>> id(d1) 430542 # Your memory address may differ. >>> id(d2) 430542 # d2 is a reference to the same Date that d1 references. >>> id(d3) 413488 # d3 is a reference to a different Date in memory. # The new == operator tests whether the internal date is the same. >>> d1 == d2 True # Both refer to the same object, so their internal # data is also the same. >>> d1 == d3 True # These variables refer to different objects, but # their internal data is the same!
Notice that we now get
True
when we evaluated1 == d3
. That’s because the new__eq__
method compares the internals of the objects to whichd1
andd3
refer, rather than comparing the memory addresses of the objects. -
Write a method
is_before(self, other)
that returnsTrue
if the called object represents a calendar date that occurs before the calendar date that is represented byother
. Ifself
andother
represent the same day, or ifself
occurs afterother
, the method should returnFalse
.Notes:
- This method is similar to the
__eq__
method that you have written in that you will need to compare the years, months, and days to determine whether the calling object comes beforeother
.
Examples:
>>> ny = Date(1, 1, 2019) >>> d1 = Date(11, 15, 2018) >>> d2 = Date(3, 24, 2018) >>> tg = Date(11, 22, 2018) >>> ny.is_before(d1) False >>> d1.is_before(ny) True >>> d1.is_before(d2) False >>> d2.is_before(d1) True >>> d1.is_before(tg) True >>> tg.is_before(d1) False >>> tg.is_before(tg) False
- This method is similar to the
-
Write a method
is_after(self, other)
that returnsTrue
if the calling object represents a calendar date that occurs after the calendar date that is represented byother
. Ifself
andother
represent the same day, or ifself
occurs beforeother
, the method should returnFalse
.Notes:
- There are two ways of writing this method. You can either emulate
your code for
is_before
OR you can think about how you could call__eq__
(==
) andis_before
to make writing this method very simple.
Examples:
>>> ny = Date(1, 1, 2019) >>> d1 = Date(11, 15, 2018) >>> d2 = Date(3, 24, 2018) >>> tg = Date(11, 22, 2018) >>> ny.is_after(d1) True >>> d1.is_after(ny) False >>> d1.is_after(d2) True >>> d2.is_after(d1) False >>> d1.is_after(tg) False >>> tg.is_after(d1) True >>> tg.is_after(tg) False
- There are two ways of writing this method. You can either emulate
your code for
-
Write a method
days_between(self, other)
that returns an integer that represents the number of days betweenself
andother
.Notes:
- This method should not change
self
nor should it changeother
during its execution. -
The sign of the return value is important! In particular:
- If
self
andother
represent the same calendar date, this method should return 0. - If
self
is beforeother
, this method should return a negative integer equal to the number of days between the two dates. - If
self
is afterother
, this method should return a positive integer equal to the number of days between the two dates.
- If
Suggested Approach:
-
Since this method should not change the original objects, you should first use the
copy
method to create true copies ofself
andother
. -
Then, use
is_before
oris_after
to figure out which date comes first. -
You can use the
advance_one
method that you have already written in a similar way to how you used it in theadvance_n
method to count up from one date to another. However, unlike in that method, indays_between
it is not clear how many times you need to calladvance_one
to get an appropriate count from one date to the other. What kind of loop is well-suited for this kind of problem? -
Once you know how many days separate the two values, you can again use
is_before
oris_after
to figure out whether the returned result should be positive or negative. -
You should not try to subtract years, months, and days between the two dates. This technique is too prone to mistakes.
-
You should also not try to use
advance_n
to implement yourdays_between
method. Checking all of the possible values for the number of days between will be too complicated!
Examples:
>>> d1 = Date(4, 8, 2019) >>> d2 = Date(5, 7, 2019) >>> d2.days_between(d1) 29 >>> d1.days_between(d2) -29 >>> d1 # Make sure the original objects did not change. 04/08/2019 >>> d2 05/07/2019 # Here are two that pass over a leap day. >>> d3 = Date(12, 1, 2019) >>> d4 = Date(3, 15, 2020) >>> d4.days_between(d3) 105
- This method should not change
-
Write a method
day_name(self)
that returns a string that indicates the name of the day of the week of theDate
object that calls it. In other words, the method should return one of the following strings:'Monday'
,'Tuesday'
,'Wednesday'
,'Thursday'
,'Friday'
,'Saturday'
,'Sunday'
.Suggested Approach:
-
Try using the
days_between
method from a known date. For example, how could it help to know the number of days between the called object and aDate
object representing Monday, April 8, 2019? How might the modulus (%
) operator help? -
Calling
days_between
will give you a negative number if theDate
you are operating on comes before the known date used byday_name
. You should leave the result as a negative number in such cases; you should not take its absolute value. -
It will be useful to copy and paste the following list to the first line of your method:
day_names = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
Examples:
>>> d = Date(4, 8, 2019) >>> d.day_name() 'Monday' >>> Date(4, 9, 2019).day_name() 'Tuesday' >>> Date(4, 10, 2019).day_name() 'Wednesday' >>> Date(1, 1, 2100).day_name() 'Friday' >>> Date(7, 4, 1776).day_name() 'Thursday'
-
Problem 3: Date
clients
20 points; pair-optional or group-of-three-optional
Now that you have written a functional Date
class, we will put it to use!
Remember that the Date
class is only a blueprint, or template, for how
Date
objects should behave. We can now create Date
objects according to
that template and use them in client code.
Getting started
To start, open a new file in VS Code and save it as ps12pr3.py
. Put all of
the code that you write for this problem in this file. Don’t forget to include
appropriate comments at the top of the file, and a docstring for
your function.
IMPORTANT: Since your clients will need to construct Date
objects,
you need to import the Date
class. Therefore, make sure that ps12pr3.py
is in the same directory as ps12pr2.py
, and include the following
statement at the top of ps12pr3.py
:
from ps12pr2 import Date
Your tasks
-
Write a function named
get_age_on(birthday, other)
that accepts twoDate
objects as parameters: one to represent a person’s birthday, and one to represent an arbitrary date. The function should then return the person’s age on that date as an integer.Notes:
- You can assume that the
other
parameter will represent a date on or after thebirthday
date. - It may be helpful to construct a new
Date
object that represents the person’s birthday in the year ofother
. That way, you can determine whether the person’s birthday has already passed in the year ofother
, and use that information to calculate the age.
Example:
>>> birthday = Date(6, 29, 1994) >>> d1 = Date(2, 10, 2014) >>> get_age_on(birthday, d1) 19 >>> d2 = Date(11, 10, 2014) >>> get_age_on(birthday, d2) 20
- You can assume that the
-
Write a function
print_birthdays(filename)
that accepts a stringfilename
as a parameter. The function should then open the file that corresponds to that filename, read through the file, and print some information derived from that file.More specifically, the function should assume that the file in question contains information about birthdays in lines of the following format:
name,month,day,year
In other words, each line of the file contains comma-separated birthday data.
The function should read this file line-by-line, and print the person’s name, birthday, and the day of the week on which the person was born in the following format:
name (mm/dd/yyyy) (day)
For example, the file birthdays.txt contains the following data:
George Washington,2,22,1732 Abraham Lincoln,2,12,1809 Susan B. Anthony,2,15,1820 Franklin D. Roosevelt,1,30,1882 Eleanor Roosevelt,10,11,1884
Therefore, calling
print_birthdays
with this filename should print the following information:>>> print_birthdays('birthdays.txt') George Washington (02/22/1732) (Friday) Abraham Lincoln (02/12/1809) (Sunday) Susan B. Anthony (02/15/1820) (Tuesday) Franklin D. Roosevelt (01/30/1882) (Monday) Eleanor Roosevelt (10/11/1884) (Saturday)
Notes:
- For full-credit, the format of the printed text should exactly match the formatting scheme described above, including spaces and parentheses.
- You should process the text file using the line-by-line technique shown in lecture.
- For every line of the file, you will need to create a
Date
object and invoke the appropriate methods on the object to get the information needed. - Originally, the components of the date that you obtain from the
file will be in string form. You will need to convert them to
integers before you pass them into the
Date
constructor, and you can use theint
function for this purpose. - You can get a string representation of a
Date
object namedd
using the expressionstr(d)
. - Remember that you can concatenate strings together using the string
concatentation operator (
+
). This operator will be helpful when you try to wrap parts of the output in parentheses. For example:>>> '(' + 'foo' + ')' '(foo)'
Submitting Your Work
You should use Gradesope to submit the following files:
- your
ps12pr1.py
file containing your solutions for Problem 1 - your
ps12pr2.py
file containing your solutions for Problem 2 - your
ps12pr3.py
file containing your solutions for Problem 3
Warnings
-
Make sure to use these exact file names, or Gradescope will not accept your files. If Gradescope reports that a file does not have the correct name, you should rename the file using the name listed in the assignment or on the Gradescope upload page.
-
If you make any last-minute changes to one of your Python files (e.g., adding additional comments), you should run the file in VS Code after you make the changes to ensure that it still runs correctly. Even seemingly minor changes can cause your code to become unrunnable.
-
If you submit an unrunnable file, Gradescope will accept your file, but it will not be able to auto-grade it. If time permits, you are strongly encouraged to fix your file and resubmit. Otherwise, your code will fail most if not all of our tests.