Week 11: Classes II

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:15

  • 11:59

  • 12:00

  • 16:14

  • 21:56

  • 23:59

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.

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 - elif statement 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.

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 ScoreTracker object.

  • 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 include should take care of keeping track of the two highest scores.

  • Return the new object.

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 

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?

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

\[ a + b = (a + b) \operatorname{mod} 4 \]
\[ ab = (ab) \operatorname{mod} 4 \]

For example

\[3 + 2 = (3 + 2) \operatorname{mod} 4 = 5 \operatorname{mod} 4 = 1\]
\[3 \cdot 2 = (3 \cdot 2) \operatorname{mod} 4 = 6 \operatorname{mod} 4 = 2\]

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()

Problem Solving#