In-Class#

Coding Practice#

Code 11.1: 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.2: 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.3: 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#

Problem 11.4: Overdraft Account#

Recall from last week the class BankAccount. Find the file with your solution from the last week, and extend it.

You should now make a subclass to it named OverdraftAccount. This subclass should allow the user to have a negative balance, as long as the sum of the balance and the overdraft limit is not negative.

Write the class definition for the subclass OverdraftAccount, which inherits from BankAccount. Each instance of the subclass should store the overdraft limit. The constructor of the new class should take as input the initial balance and the overdraft limit (a non-negative integer). The withdraw method should ensure that the overdraft limit is not exceeded. If the withdrawal is not possible, the balance should remain unchanged. As before, the withdraw method should return the amount withdrawn. You should modify the necessary methods of the class to achieve this behavior, and inherit the rest of the methods from the parent class.

Here is an example of using the class.

>>> my_account = OverdraftAccount(0, 500)
>>> my_account.get_balance()
0
>>> my_account.deposit(1000)
>>> my_account.get_balance()
1000
>>> my_account.withdraw(1300)
1300
>>> my_account.get_balance()
-300
>>> my_account.withdraw(500)
0
>>> my_account.get_balance()
-300

Here, the initial balance is 0, and the overdraft limit is 500. First, 1000 is deposited. Then, 1300 is withdrawn. This is allowed since it brings the balance to -300 which is above -500. Finally, 500 is attempted to be withdrawn, but that would bring the balance below -500. So, this withdrawal is not possible, and the balance remains unchanged.

The filename and requirements are in the box below.

overdraft_account.py

OverdraftAccount()

A class that represents a bank account allowing overdraft.

__init__(balance, overdraft_limit)

Initialize the bank account with a balance and an overdraft limit.

Parameters:

  • balance

float

Initial balance of the bank account.

  • overdraft_limit

float

Overdraft limit of the bank account.

withdraw(amount)

Withdraw an amount from the account.

Parameters:

  • amount

float

Amount to withdraw.

Returns:

  • float

Amount withdrawn, 0 if not possible.

Problem 11.5: Limited Event Manager#

Remember the class EventManager from last week? Find the file with your solution from the last week, and extend it.

We want to create a subclass of the EventManager class. This subclass should prevent the registration of participants if the limit on the number of participants has been reached.

Write the class definition for the subclass LimitedEventManager, which inherits from EventManager. Each instance of the subclass should store the registration limit. The constructor of the new class should take as input the registration limit (a non-negative integer). The register method should ensure that the number of registrations does not exceed the limit. If the registration is not possible because the limit is reached, the method return -2. If the limit is not reached, the registration method should act as specified in EventManager. You should modify the necessary methods of the class to achieve this behavior, and inherit the rest of the methods from the parent class.

Here is an example of using the class.

>>> my_event = LimitedEventManager(3)
>>> my_event.register('Mike')
1
>>> my_event.register('Emily')
1
>>> my_event.register('Sara')
1
>>> my_event.register('Peter')
-2
>>> print(my_event.get_num_registered())
3

In this example an event manager with limit 3 is created. Initially, there are no registrations. Then, Mike is registered, and then Emily and Sara are registered, with registration method signaling success by returning 1. Then, an attempt to register Peter is made, but this is not possible as 3 people are already signed up, and the method returns -2. Finally, we get and print the number of registrations.

The filename and requirements are in the box below.

limited_event_manager.py

LimitedEventManager()

A class that represents an event with limited number registrations.

__init__(limit)

Initialize the limited event with limit.

Parameters:

  • limit

int

The limit for the number of registrations.

register(name)

Register the user if limit is not reached.

Parameters:

  • name

str

The name of the person wishing to register.

Returns:

  • int

1 if successful, -1 if already registered, -2 if limit reached.