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.
Check
For now, you code should work similar to example bellow.
>>> 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
-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.
Check
Now, you code should work similar to 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
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.
Check
Now, you code should work similar to example bellow.
>>> 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.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
Check
The new class should work similar to example bellow.
>>> 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.
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
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()
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:
|
|
Initial balance of the bank account. |
|
|
Overdraft limit of the bank account. |
withdraw(amount)
Withdraw an amount from the account.
Parameters:
|
|
Amount to withdraw. |
Returns:
|
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:
|
|
The limit for the number of registrations. |
register(name)
Register the user if limit is not reached.
Parameters:
|
|
The name of the person wishing to register. |
Returns:
|
1 if successful, -1 if already registered, -2 if limit reached. |