
Warning
This page shows last year’s version. We might still make small changes, but you’re welcome to take a look. We’ll remove this notice once the page is final.
Week 11: In-Class#
Demo#
Demo 11.1: World Time#
In addition to the 24-hour time we implemented in w11_my_time, the US uses a 12-hour clock format. In the 12-hour format time is written using the numbers 1 to 12 (without leading zero) to represent the hours, the numbers 00 to 59 to represent the minutes, and “AM” or “PM” to indicate if it is before noon or after noon that day. Noon is written as 12:00 PM, and midnight as 12:00 AM.
Problem Analysis
How is 24-hour time displayed in 12-hour format? Consider the following cases:
00:1511:5912:0016:1421:5623:59
Demo
Write a class WorldTime that represents inherits from the previously defined MyTime class. The WorldTime class should take an additional parameter during initialization to specify whether the time should be displayed in 12-hour or 24-hour format as a string ('12' or '24'). Additionally, override the __str__ method to return a string representation of the time in either 12 or 24 hour notation.
Finally, we want to be able to add two WorldTime objects together using the + operator, which should return a new WorldTime object representing the sum of their respective hours and minutes. The addition should correctly handle overflow of minutes and hours.
The code cell below shows the expected behavior.
>>> my_24_time = WorldTime(11, 15, '24')
>>> my_12_time = WorldTime(11, 59, '12')
>>> my_24_time.increment_minutes(50)
>>> str(my_24_time)
'12:05'
>>> str(my_12_time)
'11:59 AM'
>>> str(my_12_time + my_24_time)
'12:04 AM'
>>>
>>> my_12_time.increment_minutes(5)
>>> str(my_12_time)
'12:04 PM'
>>> my_12_time.increment_minutes(58)
>>> str(my_12_time)
'1:02 PM'
>>> my_12_time.increment_hours(10)
>>> str(my_12_time)
'11:02 PM'
>>> my_12_time.increment_minutes(59)
>>> str(my_12_time)
'12:01 AM'
>>> my_12_time.increment_hours(25)
>>> str(my_12_time)
'1:01 AM'
The class requirements are:
my_time.pyWorldTime()
A class to represent time from in 12 or 24 hour format.
__init__(hours, minutes, clock_type)
Initialize a WorldTime object.
Parameters:
|
|
The hour value (0 to 23). |
|
|
The minute value (0 to 59). |
|
|
The type of clock, either ‘12’ or ‘24’. |
__str__()
Return a string representing the time.
Returns:
|
The time in the specified clock format. |
__add__(other)
Add two WorldTime objects. The resulting time is based on the first object’s clock type.
Parameters:
|
|
Another WorldTime object to add. |
Returns:
|
A new WorldTime object representing the sum. |
Use the following script to check your function test_world_time.py. If your function fails the test in this script, it would also fail if you could hand it in.
Coding Practice#
Code 11.2: Score Tracker#
You need a class which keeps track of the scores of a game. You want only two highest scores to be stored, together with the names of the players who achieved them. When initiated, the class should be empty, and the scores should be included one by one.
Let’s make a class ScoreTracker step by step.
First, consider what the class should have as attributes. You need to keep track of four things: the highest score, the second highest score, the name of the player who achieved the highest score, and the name of the player who achieved the second highest score. Deciding on which attributes to use is a programmer’s task: it can be 4 separate attributes, or a 2-element list (or tuple) for scores and a 2-element list for names, or a dictionary. You can choose any suitable representation, but we will assume that you have attributes score_1, score_2, name_1, and name_2.
Write the code where you define the class ScoreTracker. The constructor should initialize the attributes score_1 and score_2 to 0 and the attributes name_1 and name_2 to an empty string.
Write the code for the method include which takes as input the name of the player and the score of the player. For now, this method should always do the same: move the player which is currently the first to the second place, and replace the first player with the new player. We will later modify this method to keep track of the two highest scores.
Write the code for the method __str__ such that it returns a nice string representation of the object. It is up to you how you want to represent the object as a string, you can take inspiration from the examples below.
Check
For now, your code should work similar to the example below.
>>> scores = ScoreTracker()
>>> scores.include(105, 'Alice')
>>> print(scores)
HIGH SCORES
Winner 105 Alice
Runner up 0
>>> scores.include(98, 'Bob')
>>> print(scores)
HIGH SCORES
Winner 98 Bob
Runner up 105 Alice
>>> scores.include(108, 'Alice')
>>> print(scores)
HIGH SCORES
Winner 108 Alice
Runner up 98 Bob
Let’s now modify the include method to keep track of the two highest scores. Notice that there are two situations, where the score needs to be updated:
When the new score is higher or equal than the highest score.
When the new score is higher or equal than the second highest score but lower than the highest score. Write an
if-elifstatement to handle these two cases.
In the first case, you should the same as before: move the first player to the second place and replace the first player with the new player. In the second case, you should only replace the second player with the new player.
Check
Now, your code should work similar to the example below.
>>> scores = ScoreTracker()
>>> scores.include(105, 'Alice')
>>> scores.include(98, 'Bob')
>>> scores.include(108, 'Alice')
>>> scores.include(106, 'Charlie')
>>> print(scores)
HIGH SCORES
Winner 108 Alice
Runner up 106 Charlie
Finally, you want to be able to combine several ScoreTracker objects, and get the overall highest scores. Let’s overload the + operator to combine two ScoreTracker objects. The result should be a new ScoreTracker object, which keeps track of the highest scores of the two input objects.
You need to define the __add__ method in the class ScoreTracker. This method should take another ScoreTracker object as input and return a new ScoreTracker object. Notice that you can use the methods you have already defined in the class to achieve this.
First, create a new
ScoreTrackerobject.Include four players to the new object: the two players from the first object and the two players from the second object. Your implementation of
includeshould take care of keeping track of the two highest scores.Return the new object.
Check
Now, your code should work similar to the example below.
>>> wednesday_scores = ScoreTracker()
>>> wednesday_scores.include(105, 'Alice')
>>> wednesday_scores.include(98, 'Bob')
>>> print('\nWEDNESDAY', wednesday_scores)
WEDNESDAY HIGH SCORES
Winner 105 Alice
Runner up 98 Bob
>>>
>>> thursday_scores = ScoreTracker()
>>> thursday_scores.include(102, 'Charlie')
>>> thursday_scores.include(99, 'Alice')
>>> print('\nTHURSDAY', thursday_scores)
THURSDAY HIGH SCORES
Winner 102 Charlie
Runner up 99 Alice
>>>
>>> combined_scores = wednesday_scores + thursday_scores
>>> print('\nCOMBINED', combined_scores)
COMBINED HIGH SCORES
Winner 105 Alice
Runner up 102 Charlie
Code 11.3: Unique Score Tracker#
The score tracker you have implemented in the previous task keeps may end up having the same player in both the first and the second place. This is not very interesting, so you want to modify the class to keep track of unique players. That is, the tracker should still keep track of the two highest scores, but if the same player achieves both scores, the player should only be listed only as a winner, and the second place should be given to another player.
Instead of modifying the ScoreTracker class, you will create a new class UniqueScoreTracker which inherits from ScoreTracker. Notice that the only difference between the two classes is the include method, which needs to include the check for unique players.
You can include name checks before or after the score checks. One way where name checks happen after score checks, is sketched below.
if score is larger or equal to the highest score:
if name is already the winner:
Update the score of the winner
else:
Include the new result as the winner, move the current winner to runner-up
elif score is larger or equal to the runner-up score:
if name is not the winner:
Include the new score as runner-up
Check
The new class should work similar to example below.
>>> scores = UniqueScoreTracker()
>>> scores.include(105, 'Alice')
>>> scores.include(98, 'Bob')
>>> scores.include(108, 'Alice')
>>> print(scores)
HIGH SCORES
Winner 108 Alice
Runner up 98 Bob
>>> scores.include(99, 'Alice')
>>> print(scores)
HIGH SCORES
Winner 108 Alice
Runner up 98 Bob
>>> scores.include(109, 'Bob')
>>> print(scores)
HIGH SCORES
Winner 109 Bob
Runner up 108 Alice
Test whether you can combine two UniqueScoreTracker objects using the + operator. This should work without error because the UniqueScoreTracker class inherits the __add__ method from the ScoreTracker class. However, if you test thoroughly, you will notice that the combined score may contain the same player in both the first and the second place. To figure out how this is possible, try to print the type of the combined score. Where in the code do you define the object which is returned by the __add__ method? Why is this a problem?
Answer
The problem is that the __add__ method in the ScoreTracker class uses ScoreTracker() to create a new object (at least if you followed our hints). This means that the UniqueScoreTracker inherits a method which creates a ScoreTracker object. A solution would be to override the __add__ method in the UniqueScoreTracker class.
For a slightly more advanced solution, check Advanced 11.2: Smart Object Creation.
Code 11.4: Modular Arithmetic#
In mathematics, a ring is a set equipped with addition and multiplication operations satisfying certain properties. Consider for example the set \(\mathbb{Z}/4 = \{0,1,2,3\}\) where we define addition and multiplication as
For example
Create a class named IntegerMod4, that takes an integer as input, computes the modulus 4 of the integer, and stores it as an attribute. Override the __str__()
method, such that it returns a string containing the value.
You should be able to use the objects of this class as below.
>>> e0 = IntegerMod4(0)
>>> e15 = IntegerMod4(15)
>>> print(e0, e15)
0 3
Implement now the addition and multiplication for the elements of this class. You should do this by overriding __add__()
and __mul__()
methods.
Use the code below to test your implementation. It should print the expected sums. Modify the code to print the products as well.
e0 = IntegerMod4(0)
e1 = IntegerMod4(1)
e2 = IntegerMod4(2)
e3 = IntegerMod4(3)
Z4 = [e0, e1, e2, e3]
for i in Z4:
for j in Z4:
print(f'{i} + {j} = {i + j}')
print()