The aim of this session is to help you master important notions in computer science.
An intelligent programming assistant such as GitHub Copilot, that you may have installed already, will be able to provide you with a solution to these exercises based only on a wisely chosen file name.
For the sake of training, we advise you to disable such tools first.
At the end of the practical activity, we suggest you to work on the exercise again with these tools activated.
Following these two steps will improve your skills both fundamentally and practically.
Also, we provide you the solutions to the exercises.
Make sure to check them only after you have a solution to the exercises, for comparison purpose!
Even if you are sure your solution is correct, please have a look at them, as they sometimes provide additional elements you may have missed.
Activity contents
1 - A blackboard
In this exercise, you’ll create a class representing a blackboard. Your blackboard:
has a suface (an attribute) as a string
provides three methods:
write(text): adds text (a string) to the current content of the blackboard as a new line
read(): returns the current content of the blackboard as a string
show(): prints the current content of the blackboard to the console
erase(): deletes the content of the blackboard.
Question. Create the class Blackboard as described above.
Solution to the exercise
In the solution, we choose to use the attribute __surface to store the content of the blackboard so that direct modification of the content is not possible.
classBlackboard:
""" Represents a blackboard with a surface that can be written on, read and erase.
"""def __init__(self) ->None:
"""
Initializes a new instance of the Blackboard class.
""" self._surface: str =""defwrite(self, text: str) ->None:
"""
Writes the specified text on the surface of the blackboard.
Parameters:
-----------
text: str
The text to be written on the surface of the blackboard.
"""if text !="":
# Add a new line if the text is not empty self._surface +='\n' self._surface += text
defread(self) -> str:
"""
Returns the text written on the surface of the blackboard.
"""return self._surface
defshow(self) ->None:
"""
Shows the text written on the surface of the blackboard.
""" print(self._surface)
deferase(self) ->None:
"""
Erases the text written on the surface of the blackboard.
"""if self._surface !="":
self._surface =""
In the solution, we choose to make the attribute surfaceprivate so that direct modification of the content is not possible.
/**
* Represents a blackboard with a surface that can be written on, read and erase.
*/publicclassBlackboard {
// The surface of the blackboard, as a private attributeprivate String surface;
/**
* Initializes a new instance of the Blackboard class.
*/publicBlackboard() {
this.surface="";
}
/**
* Writes the specified text on the surface of the blackboard.
*
* @param text: the text to be written on the surface of the blackboard
*/publicvoidwrite(String text) {
if (!text.isEmpty()) {
// Add a new line if the text is not emptythis.surface+="\n";
}
this.surface+= text;
}
/**
* Returns the text written on the surface of the blackboard.
* @return the text written on the surface of the blackboard
*/public String read() {
returnthis.surface;
}
/**
* Shows the text written on the surface of the blackboard.
*/publicvoidshow() {
System.out.println(this.surface);
}
/**
* Erases the text written on the surface of the blackboard.
*/publicvoiderase() {
this.surface="";
}
}
Question. Create a blackboard, and use its methods to write Hello, World! on it, read the message, then delete it and, if the content of the blackboard is not empty, write an error message Error: The blackboard should be empty.
Solution to the exercise
if __name__ =="__main__":
one_blackboard = Blackboard()
one_blackboard.write("Hello world")
one_blackboard.show()
one_blackboard.erase()
if one_blackboard.read() !="":
print("Error: The blackboard should be empty")
publicstaticvoidmain(String[] args) {
Blackboard blackboard =new Blackboard();
blackboard.write("Hello world!");
blackboard.show();
blackboard.erase();
if (blackboard.read() !="") {
System.out.println("Error: The blackboard should be empty");
}
}
2 - A bank account
In this exercise, you will create a class to represent a simple version of a bank account (BankAccount). Your bank account:
is identified by a number (account_number)
has the current account balance (balance) and the history of transactions made on the account (history)
provides several methods:
a constructor that initializes the account number, the balance and the history of transactions
deposit(amount: float) (or void deposit(float amount) in Java): makes deposits on the account. A deposit may only be possible if amount is a positive number. If it is not, an exception (ValueError or IllegalArgumentException in Java) should be raised
withdraw(amount: float) (or void withdraw(float amount) in Java): makes withdrawals. A withdrawal may only be possible if amount is a positive number and the account has enough money to cover the withdrawal. If it is not the case, an exception (ValueError or IllegalArgumentException in Java) should be raised
get_account_number() -> int (or int getAccountNumber() in Java): returns the account number
get_transaction_history() -> list[float] (or ArrayList<Float> getTransactionHistory() in Java): returns the set of transactions made on the account
get_balance()->float (or float getBalance() in Java): returns the current balance.
Question. Create the class BankAccount as described above.
Solution to the exercise
classBankAccount:
"""
Represents a bank account with a balance and a transaction history.
"""def __init__(self, account_number: int) ->None:
"""
Initializes a new instance of the BankAccount class.
Parameters:
-----------
account_number: int
The account number.
""" self._account_number: int = account_number
self._balance: float =0.0 self._history: list[float] = []
defdeposit(self, amount: float) ->None:
"""
Deposits money into the bank account.
Parameters:
-----------
amount: int
The amount to be deposited into the account.
Raises:
-------
ValueError: If the amount is not positive.
"""if amount <=0:
raiseValueError("Amount must be positive")
else:
self._balance += amount
self._history.append(amount)
defwithdraw(self, amount: float) ->None:
"""
Withdraws money from the bank account.
Parameters:
-----------
amount: float
The amount to be withdrawn from the account.
Raises:
-------
ValueError: If the amount is not positive or greater than the balance.
"""if amount <=0:
raiseValueError("Amount must be positive")
elif amount > self._balance:
raiseValueError("Amount must be less than the balance")
else:
self._balance -= amount
self._history.append(-amount)
defget_account_number(self) -> int:
"""
Returns the account number of the bank account.
"""return self._account_number
defget_balance(self) -> float:
"""
Returns the balance of the bank account.
Returns:
--------
The balance of the bank account.
"""return self._balance
defget_transaction_history(self) -> list[float]:
"""
Returns the transaction history of the bank account.
Returns:
--------
The transaction history of the bank account.
"""return self._history
import java.util.ArrayList;
/**
* Represents a bank account with a balance and a transaction history.
*/publicclassBankAccount {
privatefinalint accountNumber; // final keyword makes the attribute immutableprivatefloat balance;
privatefinal ArrayList<Float> history;
/**
* Initializes a new instance of the BankAccount class.
* @param accountNumber: the account number
*/publicBankAccount(int accountNumber) {
this.accountNumber= accountNumber;
this.balance= 0;
this.history=new ArrayList<>();
}
/**
* Deposits money into the bank account.
*
* @param amount: the amount to be deposited
* @throws IllegalArgumentException if the amount is not positive
*/publicvoiddeposit(float amount) {
if (amount <= 0) {
thrownew IllegalArgumentException("Amount must be positive");
}
this.balance+= amount;
this.history.add(amount);
}
/**
* Withdraws money from the bank account.
*
* @param amount: the amount to be withdrawn
* @throws IllegalArgumentException if the amount is not positive or greater than the balance
*/publicvoidwithdraw(float amount) {
if (amount <= 0) {
thrownew IllegalArgumentException("Amount must be positive");
}
if (this.balance< amount) {
thrownew IllegalArgumentException("Insufficient funds");
}
this.balance-= amount;
this.history.add(-amount);
}
/**
* Returns the account number of the bank account.
* @return the account number
*/publicintgetAccountNumber() {
returnthis.accountNumber;
}
/**
* Returns the balance of the bank account.
* @return the balance
*/publicfloatgetBalance() {
returnthis.balance;
}
/**
* Returns the transaction history of the bank account.
* @return the transaction history
*/public ArrayList<Float>getTransactionHistory() {
returnthis.history;
}
}
Question. Create a bank account, make some deposits and withdrawals with positive and negative amounts and then print the transaction history and the balance. Verify that exceptions are raised as expected.
Solution to the exercise
if __name__ =="__main__":
my_account = BankAccount(1)
my_account.deposit(500)
my_account.deposit(200)
try:
my_account.deposit(-50)
exceptValueErroras e:
print("ERROR", e)
try:
my_account.withdraw(10000)
exceptValueErroras e:
print("ERROR", e)
try:
my_account.withdraw(-50)
exceptValueErroras e:
print("ERROR", e)
my_account.withdraw(100)
print("Transaction history:", my_account.get_transaction_history()) # Should give [500, 200, -100] print("Balance:", my_account.get_balance()) # Should give 600
/**
* Represents a bank account with a balance and a transaction history.
*/publicstaticvoidmain(String[] args) {
BankAccount account =new BankAccount(1);
try {
myAccount.deposit(500);
myAccount.withdraw(200);
myAccount.deposit(-50);
myAccount.withdraw(10000);
myAccount.withdraw(-50);
myAccount.deposit(100);
System.out.println("Balance: "+ myAccount.getTransactionHistory()); // Should give [500, 200, -100] System.out.println("Transaction history: "+ myAccount.getBalance()); // Should give 600 } catch (Exception e) {
System.out.println("ERROR "+ e);
}
}
3 - A bank account and its owner
Let us suppose that we want to know for a bank account who is its owner and for each person what are his/her accounts. To do this, you need to:
modify your BankAccount class to add an owner attribute as a Person who holds the account. The account holder must be specified when the account is created (otherwise an exception (ValueError or IllegalArgumentException in Java) is raised). Also, a get_owner() (or getOwner() in Java) method must be provided by the class.
create a Person class that caracterizes a person with his/her firstname, lastname, age and all the bank accounts he/she helds (accounts attribute). The value of the lastname attribute should always be in uppercase. Finally, it will be possible to add or remove an account.
BankAccount class modifications.
The constructor of the class must be in the form __init__(self, account_number: int, owner: Person). It initializes the _owner attribute of the class with the value of the new parameter if it is not None (otherwise, an exception ValueError is raised) and asks the owner to add the new account to his/her list of accounts.
The get_owner(self) -> Person method returns the value of the _owner attribute.
Add methods __str__(self) -> str and __repr__(self) -> str. Both return a string representation of the bank account and should include the account number, the owner, the balance and the number of transactions done. As mentioned in the course on OOP syntax, both are special methods in Python. To understand the difference between __str__() and __repr__() you can refer to the documentation.
Person class.
The class will have the following attributes: _firstname, _lastname, _age and _accounts. The last one will be a list of bank accounts held by the person.
The constructor must be in the form __init__(self, name: str, firstname: str, age: int). It initializes all attributes of the class, with the _lastname attribute being in uppercase.
The add_account(self, account: BankAccount) -> None method adds account to the list of bank accounts of the owner only if it is not already in the list.
The remove_account(self, account: BankAccount) -> None method removes account from the list of bank accounts of the owner if it exists.
A method fullname(self) -> str returns a string as the concatenation of the _firstname and _lastname attributes.
A method get_accounts(self) -> list[BankAccount] returns the value of the _accounts attribute.
A method __str__(self) -> str returns a string representation of the person that includes his/her name, age and the number of accounts he/she owns.
A method __eq__(self, other: object) -> bool compares two persons and returns True if they are equal (firstname, lastname and age), False otherwise. To learn more about the special method __eq__() you can refer to the documentation.
Write a __main__ function. In a separate main.py file, it creates a person and three accounts, makes some transactions in at least one of them and then remove it. Finally, show the balance and transactions made in all the bank accounts of the person. Use the print() method to be sure that your code works. Optional Create a new person with the same firstname, lastname and age as the first one. Verify that the owner of the created bank accounts is the new created person. What does it mean?
Warning
In order for your code to execute, you will need to add the following lines of code at the beginning of the person.py file:
from __future__ import annotations
if TYPE_CHECKING: from bankaccount import BankAccount.
Both lines together avoid errors related to cyclic references. In our case, the Person class references the BankAccount class, and the BankAccount class references the Person class. To break this reference cycle, type hints are deferred by disabling their runtime evaluation, and imports are restricted to the type-checking phase.
More precisely:
the first line imports the annotations module from the __future__ package, telling Python to treat type hints as string literals rather than evaluating them immediately at runtime,
in the second line, the TYPE_CHECKING constant is a special boolean defined in the typing module. It is True only during static type checking (e.g., when using MyPy). As the actual import of the BankAccount class is wrapped in a conditional check against TYPE_CHECKING the class will be imported during type checking and not during the program’s execution.
The constructor of the class must be in the form public BankAccount(int accountNumber, Person owner). It initializes the owner attribute of the class with the value of the new parameter if it is not null (otherwise, an exception IllegalArgumentException is raised) and asks the owner to add the new account to his/her list of accounts.
A method public Person getOwner() returns the value of the owner attribute.
Add a method @Override public String toString() that returns a string representation of the bank account. The string should include the account number, the owner, the balance and the number of transactions done.
Solution to the exercise
from person import Person
classBankAccount:
"""
Represents a bank account with a number, an owner, a balance, and a transaction history.
"""def __init__(self, account_number: int, owner: Person) ->None:
"""
Initializes a new instance of the BankAccount class. Adds the account to the owner's list of accounts.
Parameters:
-----------
account_number: int
The account number.
owner: Person
The owner of the account.
Raises:
-------
ValueError: If the owner is None.
"""if owner isNone:
raiseValueError("Owner must be specified")
self._account_number: int = account_number
self._owner: Person = owner
self._balance: float =0 self._history: list[float] = []
# The owner adds the new account to his/her list of accounts owner.add_account(self)
defdeposit(self, amount: float) ->None:
"""
Deposits the specified amount to the balance of the bank account.
Parameters:
-----------
amount: float
The amount to deposit.
Raises:
-------
ValueError: If the amount is not positive.
"""if amount <0:
raiseValueError("Amount must be positive")
self._balance += amount
self._history.append(amount)
defwithdraw(self, amount: float) ->None:
"""
Withdraws the specified amount from the balance of the bank account.
Parameters:
-----------
amount: float
The amount to withdraw.
Raises:
-------
ValueError: If the amount is not positive or greater than the balance.
"""if0< amount <= self._balance:
self._balance -= amount
self._history.append(-amount)
else:
raiseValueError("Amount must be positive and less than the balance")
defget_account_number(self) -> int:
"""
Returns the account number of the bank account.
"""return self._account_number
defget_balance(self) -> float:
"""
Returns the balance of the bank account.
Returns:
--------
The balance of the bank account.
"""return self._balance
defget_transaction_history(self) -> list[float]:
"""
Returns the transaction history of the bank account.
Returns:
--------
The transaction history of the bank account.
"""return self._history
defget_owner(self) -> Person:
"""
Returns the owner of the bank account.
Returns:
--------
The owner of the bank account.
"""return self._owner
def __str__(self) -> str:
returnf"Account number {self._account_number} from {self._owner.fullname()} has {self._balance} € and has done {len(self._history)} transactions"def __repr__(self) -> str:
returnf"Account number {self._account_number}: {self._owner.fullname()} has {self._balance} € and has done {len(self._history)} transactions"
import java.util.ArrayList;
/**
* Represents a bank account with a balance and a transaction history.
*/publicclassBankAccount {
privatefinal Person owner;
privatefinalint accountNumber;
privatefloat balance;
privatefinal ArrayList<Float> history;
/**
* Initializes a new instance of the BankAccount class.
*
* @param accountNumber: the account number
*/publicBankAccount(int accountNumber, Person owner) {
if (owner ==null) {
thrownew IllegalArgumentException("Owner must be specified");
}
this.accountNumber= accountNumber;
this.owner= owner;
this.balance= 0;
this.history=new ArrayList<>();
owner.addAccount(this);
}
/**
* Deposits money into the bank account.
*
* @param amount: the amount to be deposited
* @throws IllegalArgumentException if the amount is not positive
*/publicvoiddeposit(float amount) {
if (amount <= 0) {
thrownew IllegalArgumentException("Amount must be positive");
}
this.balance+= amount;
this.history.add(amount);
}
/**
* Withdraws money from the bank account.
*
* @param amount: the amount to be withdrawn
* @throws IllegalArgumentException if the amount is not positive or greater than the balance
*/publicvoidwithdraw(float amount) {
if (amount <= 0) {
thrownew IllegalArgumentException("Amount must be positive");
}
if (this.balance< amount) {
thrownew IllegalArgumentException("Insufficient funds");
}
this.balance-= amount;
this.history.add(-amount);
}
/**
* Returns the account number of the bank account.
*
* @return the account number
*/publicintgetAccountNumber() {
returnthis.accountNumber;
}
/**
* Returns the balance of the bank account.
*
* @return the balance
*/publicfloatgetBalance() {
returnthis.balance;
}
/**
* Returns the transaction history of the bank account.
*
* @return the transaction history
*/public ArrayList<Float>getTransactionHistory() {
returnthis.history;
}
/**
* Returns the owner of the bank account.
*
* @return the owner
*/public Person getOwner() {
returnthis.owner;
}
/**
* Returns a string representation of the bank account.
*
* @return a string representation of the bank account
*/// The annotation indicates that the following method overrides a method in the superclass, here Object.// This annotation is optional, but it is very good practice to use it.@Overridepublic String toString() {
return"Account number "+this.accountNumber+" from "+this.owner.fullName() +" has "+this.balance+" € and has done "+this.history.size() +" transactions";
}
}
from __future__ import annotations
from typing import*if TYPE_CHECKING:
from bankaccount import BankAccount
classPerson:
"""Represents a person with a name, firstname, age and the list of bank accounts."""def __init__(self, name: str, firstname: str, age: int) ->None: # The constructor"""Constructs a new Person object with the specified lastname, firstname, age, and his/her accounts.""" self._lastname: str = name.upper()
self._firstname: str = firstname
self._age: int = age
self._accounts: list[BankAccount] = []
deffullname(self) -> str:
returnf"{self._firstname}{self._lastname}"defget_accounts(self) -> list[BankAccount]:
"""Returns the list of bank accounts of the person."""return self._accounts
defadd_account(self, account: BankAccount) ->None:
"""Adds the specified bank account to the list of bank accounts of the person if it is not already present.
Args:
account (BankAccount): The bank account to add.
"""if account notin self._accounts:
self._accounts.append(account)
defremove_account(self, account: BankAccount) ->None:
"""Removes the specified bank account from the list of bank accounts of the person.
Args:
account (BankAccount): The bank account to remove.
"""if account in self._accounts:
self._accounts.remove(account)
def __str__(self) -> str:
"""Returns a string representation of the Person object."""returnf"{self._firstname}{self._lastname} ({self._age} old) has {len(self._accounts)} bank accounts"def __eq__(self, other: object) -> bool:
"""Compares two Person objects.
Args:
other (object): The object to compare with.
Returns:
bool: `True` if the objects are equal, `False` otherwise.
Two persons are equal if they have the same lastname, firstname and age.
"""# Both objects should be instances of the same class (Person)ifnot isinstance(other, Person):
returnFalsereturn (
self._firstname == other._firstname
and self._lastname == other._lastname
and self._age == other._age
)
import java.util.ArrayList;
/**
* Represents a person with a name, firstname, age and the list of bank accounts.
*/publicclassPerson {
privatefinal String firstName;
privatefinal String lastName;
privateint age; // not final because it can changeprivatefinal ArrayList<BankAccount> accounts;
/**
* Initializes a new instance of the Person class with the specified first name, last name, and age.
*
* @param lastName the last name
* @param firstName the first name
* @param age the age
*/publicPerson(String lastName, String firstName, int age) {
this.lastName= lastName.toUpperCase();
this.firstName= firstName;
this.age= age;
this.accounts=new ArrayList<>();
}
/**
* Returns the full name of the person.
*
* @return the full name
*/public String fullName() {
returnthis.firstName+" "+this.lastName;
}
/**
* Returns a list that contains all the bank accounts of the person.
*
* @return the list of bank accounts
*/public ArrayList<BankAccount>getAccounts() {
return accounts;
}
/**
* Add a bank account to the person.
* If the account is already added, it will not be added again.
*
* @param account the account to add
*/publicvoidaddAccount(BankAccount account) {
if (!this.accounts.contains(account)) {
this.accounts.add(account);
}
}
/**
* Remove a bank account from the person, if it exists.
*
* @param account the account to remove
*/publicvoidremoveAccount(BankAccount account) {
this.accounts.remove(account);
}
/**
* Returns a string representation of the person.
* @return a string representation of the person
*/// The annotation indicates that the following method overrides a method in the superclass, here Object.// This annotation is optional, but it is very good practice to use it.@Overridepublic String toString() {
return firstName +" "+ lastName +" ("+ age +" old) has "+ accounts.size() +" bank accounts";
}
/**
* Compares two Person objects.
*
* @param obj the object to compare
* @return true if the objects are equal, false otherwise.
* Two persons are equal if they have the same lastname, firstname and age.
*/@Overridepublicbooleanequals(Object obj) {
// First, check if the object given is an instance of Personif (obj instanceof Person) {
// If it is, cast it to a Person object Person person = (Person) obj;
// Then, compare the fields of the two objectsreturnthis.firstName.equals(person.firstName) &&this.lastName.equals(person.lastName) &&this.age== person.age;
}
returnfalse;
}
}
from bankaccount import BankAccount
from person import Person
if __name__ =="__main__":
# Create a person alice_weber = Person("Weber", "Alice", 33)
print(alice_weber) # Should give no bank account# Create three bank accounts for Alice alice_account1 = BankAccount(1, alice_weber)
alice_account2 = BankAccount(2, alice_weber)
alice_account3 = BankAccount(3, alice_weber)
print(alice_weber) # Should give three bank accounts# Make some transactions alice_account1.deposit(500)
alice_account2.deposit(1000)
print(alice_account1) # Should give 500 print(alice_account2) # Should give 1000 alice_account2.withdraw(30)
print(alice_account2) # Should give 970# Remove the second bank account from Alice's accounts alice_weber.remove_account(alice_account2)
print(alice_weber) # Should give two bank accounts# If there is no money in account 3 add someif alice_account3.get_balance() ==0:
alice_account3.deposit(5000)
print(alice_account3) # Should give 5000 and 1 transaction# Show the balance of all accounts of alice str_elements =""for element in alice_weber.get_accounts():
str_elements += str(element) +"; " print(str_elements)
# Optional. Test __eq__ method new_alice = Person("Weber", "Alice", 33)
if alice_account1.get_owner() == new_alice:
print("get_owner Success. new_alice and alice_weber are the same person")
else:
print("get_owner Failure. new_alice and alice_weber are not the same person")
publicstaticvoidmain(String[] args) {
// Create a person Person aliceWeber =new Person("Weber", "Alice", 33);
System.out.println(aliceWeber); // Should give no bank account// Create a bank account for Alice BankAccount aliceAccount1 =new BankAccount(1, aliceWeber);
// Create another bank account for Alice BankAccount aliceAccount2 =new BankAccount(2, aliceWeber);
System.out.println(aliceWeber); // Should give two bank accounts// Create a third bank account for Alice BankAccount aliceAccount3 =new BankAccount(3, aliceWeber);
// Make some transactions aliceAccount1.deposit(500)
aliceAccount2.deposit(1000)
print(aliceAccount1) // Should give 500 print(aliceAccount2) // Should give 1000 aliceAccount2.withdraw(30)
print(aliceAccount2) // Should give 970// Remove the second bank account from Alice's accounts aliceWeber.removeAccount(aliceAccount2);
System.out.println(aliceWeber); // Should give two bank accounts// If there is no money in account 3 add someif aliceAccount3.get_balance() == 0:
aliceAccount3.deposit(5000)
print(aliceAccount3) // Should give 5000 and 1 transaction// Show the balance of all accounts of alice ArrayList<BankAccount> elements = alice_weber.getAccounts();
System.out.println(elements); // Should give [alice_account1, alice_account3]// Optional. Test equals method Person new_alice =new Person("Weber", "Alice", 33);
if (alice_account1.getOwner().equals(new_alice)) {
System.out.println("getOwner Success");
} else {
System.out.println("getOwner Failure");
}
}
4 - Elected representatives
In this exercise, you’re going to consider the special case of people who are elected representatives. Elected representatives are people with a set of assistants (who are also people, of course). An elected official can hire or fire an assistant. He/she can also distribute a budget to his/her assistants: he/she divides the sum allocated to him/her equally between his/her assistants by adding money to one of the assistants’ bank accounts. More specifically, an elected official:
is a person with a new attribute _assistants (or List<Person> assistants in Java) to store his/her assistants
has 4 new methods
hire_assistant(self, assistant: Person) -> None (or void hireAssistant(Person assistant) in Java) that adds assistant to the list of assistants if it is not already in it
fire_assistant(self, assistant: Person) -> None (or void fireAssistant(Person assistant) in Java) that removes assistant from the list of assistants if it exists
get_assistants(self)-> list[Person] (or getAssistants() in Java) that returns the list of assistants
spend_allocation(self, amount: float) -> dict[str, float] (or spendAllocation(float amount) in Java) that equally distributes amount among the assistants if the amount is positive. Otherwise, an exception (ValueError or IllegalArgumentExceptionin Java) is raised. The method returns the assistants that have no bank account and the amount they should be given (by other means than bank transfer). If an assistant has more than one account, the one with the lowest balance is used.
Question. Create the class ElectedOfficial as described above. Use the classes Person and BankAccount of the previous exercise. To implement the key element of the dictionary to be returned, use the fullname of an assistant.
Question. Add a __str__() (@Override public String toString() in Java) method to describe your new class. To do this, redefine the __str__() method of the parent class.
Solution to the exercise
from person import Person
from bankaccount import BankAccount
classElectedOfficial(Person):
"""
Represents an elected official with a list of assistants.
It inherits from the Person class.
"""def __init__(self, name: str, firstname: str, age: int) ->None: # The constructor"""
Constructs a new ElectedOfficial object with the specified lastname, firstname, age
He/she has no assistants.
Parameters:
-----------
name: str
The lastname of the elected official.
firstname: str
The firstname of the elected official.
age: int
The age of the elected official.
""" super().__init__(
name, firstname, age
) # Call the constructor of the parent class self._assistants: list[Person] = []
defhire_assistant(self, assistant: Person) ->None:
"""
Adds the specified assistant to the list of assistants of the elected official if it is not already in it.
Parameters:
-----------
assistant: Person
The assistant to add.
"""if assistant isnotNone:
self._assistants.append(assistant)
deffire_assistant(self, assistant: Person) ->None:
"""
Removes the specified assistant from the list of assistants of the elected official if it exists.
Parameters:
-----------
assistant: Person
The assistant to remove.
"""if assistant in self._assistants:
self._assistants.remove(assistant)
defget_assistants(self) -> list[Person]:
"""
Returns the list of assistants of the elected official.
Returns:
--------
The list of assistants of the elected official.
"""return self._assistants
defspend_allocation(self, amount: float) -> dict[str, float]:
"""
Distributes the specified amount equally between the assistants of the elected official if the amount is positive.
If an assistant has no bank account, he/she receives no money
Parameters:
-----------
amount: float
The amount to distribute.
Returns: for each assistant with no bank account the amount to be given
Raises:
-------
ValueError: if amount is < 0
"""if amount <0:
raiseValueError("The amount to distribute should be positive")
result = {}
for assistant in self._assistants: # For each assistant assistant_accounts = assistant.get_accounts()
if len(assistant_accounts) ==0: # The assistant has no accounts yet result[assistant.fullname()] = amount / len(self._assistants)
else:
# Finding the account with the minimal balance. Option 1 min_balance_account = min(
assistant_accounts, key=lambda account: account.get_balance()
)
# Finding the account with the minimal balance. Option 2# min_balance_account = assistant_accounts[0]# for account in assistant_accounts:# if account.get_balance() < min_balance_account.get_balance():# min_balance_account = account# Deposit the corresponding money min_balance_account.deposit(amount / len(self._assistants))
return result
def __str__(self) -> str:
"""
Returns a string representation of the ElectedOfficial object.
Uses the parent method to get a string representation of the ElectedOfficial object.
"""return super().__str__() +f" and {len(self._assistants)} assistants"
// If Person class is in the same package, there is no need to import it// Otherwise:// import package.of.Person;import java.util.ArrayList;
/**
* Represents an elected official with a list of assistants.
* It inherits from the Person class.
*/publicclassElectedOfficialextends Person {
// The list of assistants of the elected officialprivatefinal ArrayList<Person> assistants;
/**
* Constructs a new ElectedOfficial object with the specified lastname, firstname, age
* He/she has no assistants.
*
* @param lastName the last name
* @param firstName the first name
* @param age the age
*/publicElectedOfficial(String lastName, String firstName, int age) {
super(lastName, firstName, age);
this.assistants=new ArrayList<>();
}
/**
* Adds the specified assistant to the list of assistants of the elected official if it is not already in it.
*
* @param assistant the assistant to add
*/publicvoidhireAssistant(Person assistant) {
if (assistant !=null) {
this.assistants.add(assistant);
}
}
/**
* Removes the specified assistant from the list of assistants of the elected official.
*
* @param assistant the assistant to remove
*/publicvoidfireAssistant(Person assistant) {
if (assistant !=null) {
this.assistants.remove(assistant);
}
}
/**
* Returns the list of assistants of the elected official.
*
* @return the list of assistants of the elected official
*/public ArrayList<Person>getAssistants() {
return assistants;
}
/**
* Distributes the specified amount equally between the assistants of the elected official if the amount is positive.
*
* @param amount the amount to distribute
*/publicvoidspendAllocation(float amount) {
if (amount > 0) {
for (Person assistant : assistants) {
assistant.addBudget(amount / assistants.size());
}
}
}
/**
* Returns a string representation of the ElectedOfficial object.
* Uses the parent method to get a string representation of the ElectedOfficial object.
* @return a string representation of the ElectedOfficial object
*/@Overridepublic String toString() {
returnsuper.toString() +" with "+ assistants.size() +" assistants";
}
}
Question. Create one elected representative and three people one with no bank account, the second one with one bank account and the last one with two bank accounts with different balances. The elected representative hire the fist assistant and try to spend an allocation of 1000 euros. Then he/she hires the second person and try to distribute 2000 euros. Finally, he/she hires the third person and distributes 1500 euros.
Solution to the exercise
if __name__ =="__main__":
# Create an elected representative michael_ducas = ElectedOfficial("Michael", "Ducas", 33)
print(
f"Elected Representative {michael_ducas}\n" ) # Should give no bank account and no assistants# Create assistants of michael_ducas. No bank account david_landon = Person("David", "Landon", 25)
# Create a second assistant. One bank account linda_lucas = Person("Lucas", "Linda", 30)
linda_lucas.add_account(BankAccount(1, linda_lucas))
# Create the third assistant. Two bank accounts yann_breizh = Person("Breizh", "Yann", 28)
yann_breizh.add_account(BankAccount(2, yann_breizh))
yann_account = BankAccount(3, yann_breizh)
yann_breizh.add_account(yann_account)
yann_account.deposit(200)
# Hire an assistant michael_ducas.hire_assistant(david_landon)
print(
f"Elected Representative {michael_ducas}\n" ) # Should give no bank account and 1 assistant# Try to distribute 1000 euros: no distributiontry:
print(michael_ducas.spend_allocation(1000))
exceptValueErroras e:
print(e)
# Show information on balance on assistants accountsfor assistant in michael_ducas.get_assistants():
assistant_accounts = assistant.get_accounts()
for account in assistant_accounts:
print(
f"Assistant {assistant.fullname()}: account nber {account.get_account_number()} with balance {account.get_balance()}\n" )
# Hire Linda Lucas michael_ducas.hire_assistant(linda_lucas)
# Try to distribute 2000 euros: account transfer to Linda# Manual to David Landontry:
print(michael_ducas.spend_allocation(2000))
exceptValueErroras e:
print(e)
# Show information on balance on assistants accountsfor assistant in michael_ducas.get_assistants():
assistant_accounts = assistant.get_accounts()
for account in assistant_accounts:
print(
f"Assistant {assistant.fullname()}: account nber {account.get_account_number()} with balance {account.get_balance()}\n" )
# Hire Yann Breizh michael_ducas.hire_assistant(yann_breizh)
# Try to distribute 1500 euros: account transfer to Linda and Yann (account 3)# Manual to David Landontry:
print(michael_ducas.spend_allocation(2000))
exceptValueErroras e:
print(e)
# Show information on balance on assistants accountsfor assistant in michael_ducas.get_assistants():
assistant_accounts = assistant.get_accounts()
for account in assistant_accounts:
print(
f"Assistant {assistant.fullname()}: account nber {account.get_account_number()} with balance {account.get_balance()}\n" )
5 - Optimize your solutions
What you can do now is to use AI tools such as GitHub Copilot or ChatGPT, either to generate the solution, or to improve the first solution you came up with!
Try to do this for all exercises above, to see the differences with your solutions.
To go further
6 - Russian Dolls
In this exercise, you are going to write a program simulating Russian dolls of various sizes. Each doll has a given size, can open or close, can contain another doll and be contained in another doll. Write a RussianDoll class containing the following methods:
a constructor that initializes attributes (size, opened, placed_in, and content). It has one argument (size) as an int: the greater the value, the bigger is the russian doll
open(): opens the doll if it is not already opened and if it is not inside another doll
close(): closes the doll if it is not already closed and if it is not inside inside another doll
place_in(p: RussianDoll) (void placeIn(RussianDoll p) in Java): places the current doll in doll p, if possible. The current doll must be closed and not already inside another doll,p must be opened and contain no doll, and it must be larger than the current doll
get_out(p: RussianDoll) (void getOut(RussianDoll p) in Java): takes the current doll out of doll p if it’s in p and if p is open
Write a program that allows you to create and manipulate doll objects.
Solution to the exercise
from __future__ import annotations
classRussianDoll:
"""
Represents a Russian doll with a size, a state (opened or closed), a content and a container.
"""def __init__(self, size: int) ->None:
"""
Initializes a new instance of the RussianDoll class.
Parameters:
-----------
size: int
The size of the doll.
""" self.size: int = size
self._opened: bool =False self._content: RussianDoll |None=None self.placed_in: RussianDoll |None=Nonedefopen(self) ->None:
"""
Opens the doll if it is not already open and if it is not inside another doll.
"""ifnot self._opened and self.placed_in isNone:
self._opened =Truedefclose(self) ->None:
"""
Closes the doll if it is not already closed and if it is not inside another doll.
"""if self._opened and self.placed_in isNone:
self._opened =Falsedefplace_in(self, p: RussianDoll) ->None:
"""
Places the current doll in doll p, if possible.
The current doll must be closed and not already inside another doll.
p must be opened and contain no doll, and it must be larger than the current doll.
Parameters:
-----------
p: RussianDoll
The doll in which to place the current doll.
"""if (
not self._opened
and self.placed_in isNoneand p._opened
and p._content isNoneand p.size > self.size
):
self.placed_in = p
p._content = self
defget_out(self, p: RussianDoll) ->None:
"""
Takes the current doll out of doll p if it's in p and if p is open.
Parameters:
-----------
p: RussianDoll
The doll from which to take out the current doll.
"""if self.placed_in == p and p._opened:
self.placed_in =None p._content =Nonedef __str__(self) -> str:
"""
Returns a string representation of the doll, including its size and state.
""" state ="open"if self._opened else"closed" content =f"contains doll {self._content.size}"if self._content else"is empty"returnf"Doll {self.size} is {state} and {content}"defvisualize_dolls(dolls: list[RussianDoll]) ->None:
"""
Prints a visualization of all the Russian dolls and their relationships.
Parameters:
-----------
dolls: list[RussianDoll]
The list of Russian dolls to visualize.
"""for doll in dolls:
container = (
f"inside doll {doll.placed_in.size}"if doll.placed_in
else"not inside any doll" )
print(f"Doll: {doll} and is {container}.")
if __name__ =="__main__":
# Create three russian dolls of different sizes russian_doll1 = RussianDoll(1)
russian_doll2 = RussianDoll(2)
russian_doll3 = RussianDoll(3)
# Lets put dolls inside one another russian_doll3.open()
russian_doll2.open()
russian_doll1.place_in(russian_doll2)
russian_doll2.close()
russian_doll2.place_in(russian_doll3)
# Visualize the relationships visualize_dolls([russian_doll1, russian_doll2, russian_doll3])
russian_doll2.get_out(russian_doll3)
# Visualize the relationships visualize_dolls([russian_doll1, russian_doll2, russian_doll3])
/**
* Represents a Russian doll with a size, a state (opened or closed), a content and a container.
*/publicclassRussianDoll{
privatefinalint size;
privateboolean opened;
private RussianDoll content;
private RussianDoll placedIn;
/**
* Initializes a new instance of the RussianDoll class.
*
* @param size the size of the doll
*/publicRussianDoll(int size) {
this.size= size;
this.opened=false;
this.content=null;
this.placedIn=null;
}
/**
* Opens the doll if it is not already open and if it is not inside another doll.
*/publicvoidopen() {
if (!opened && placedIn ==null) {
opened =true;
}
}
/**
* Closes the doll if it is not already closed and if it is not inside another doll.
*/publicvoidclose() {
if (opened && placedIn ==null) {
opened =false;
}
}
/**
* Places the current doll in doll p, if possible.
* The current doll must be closed and not already inside another doll.
* p must be opened and contain no doll, and it must be larger than the current doll.
*
* @param p the doll in which to place the current doll
*/publicvoidplaceIn(RussianDoll p) {
if (!opened && placedIn ==null&& p.opened&& p.content==null&& p.size> size) {
placedIn = p;
p.content=this;
}
}
/**
* Takes the current doll out of doll p if it's in p and if p is open.
*
* @param p the doll from which to take out the current doll
*/publicvoidgetOut(RussianDoll p) {
if (placedIn == p && p.opened) {
placedIn =null;
p.content=null;
}
}
}
7 - Meals
We want to develop a recipe management program for a restaurant. A programmer has already written the Ingredient class given below:
classIngredient:
"""
Represents an ingredient with a name, quantity, state and unit.
"""def __init__(self, name: str, quantity: int, state: str, unit: str) ->None:
"""
Initializes a new instance of the Ingredient class.
Parameters:
-----------
name: str
The name of the ingredient.
quantity: int
The quantity of the ingredient.
state: str
The state of the ingredient (raw or cooked).
unit: str
The unit of the ingredient (g, kg, ml, cl, l).
Raises:
-------
ValueError: If the quantity is negative, the unit is not valid or the state is not valid.
"""if quantity <0:
raiseValueError("Quantity must be positive")
if unit.lower() notin ("g", "kg", "ml", "cl", "l"):
raiseValueError("Unit must be 'g','kg', 'ml', 'cl', or 'l'")
if state.lower() notin ("raw", "cooked"):
raiseValueError("State must be 'raw' or 'cooked'")
self._name: str = name
self._quantity: int = quantity
self._unit: str = unit.lower()
self._state: str = state.lower()
def __str__(self) -> str:
"""
Returns a string representation of the Ingredient object.
"""returnf"{self._quantity}{self._unit}{self._name} ({self._state})"if __name__ =="__main__":
butter = Ingredient("butter", 250, "raw", "g")
milk = Ingredient("milk", 1000, "raw", "ml")
print(str(butter))
print(str(milk))
/**
* Represents an ingredient with a name, quantity, state and unit.
*/publicclassIngredient {
privatefinal String name;
privatefinalint quantity;
privatefinal String state;
privatefinal String unit;
/**
* Initializes a new instance of the Ingredient class.
*
* @param name: the name of the ingredient
* @param quantity: the quantity of the ingredient
* @param state: the state of the ingredient (raw or cooked)
* @param unit: the unit of the ingredient (g, kg, ml, cl, l)
* @throws IllegalArgumentException if the quantity is negative, the unit is not valid or the state is not valid
*/publicIngredient(String name, int quantity, String state, String unit) {
if (quantity < 0) {
thrownew IllegalArgumentException("Quantity must be positive");
}
if (!unit.toLowerCase().matches("g|kg|ml|cl|l")) {
thrownew IllegalArgumentException("Unit must be 'g','kg', 'ml', 'cl', or 'l'");
}
if (!state.toLowerCase().matches("raw|cooked")) {
thrownew IllegalArgumentException("State must be 'raw' or 'cooked'");
}
this.name= name;
this.quantity= quantity;
this.state= state.toLowerCase();
this.unit= unit.toLowerCase();
}
/**
* Returns a string representation of the Ingredient object.
* @return a string representation of the Ingredient object
*/@Overridepublic String toString() {
return quantity + unit +" "+ name +" ("+ state +")";
}
publicstaticvoidmain(String[] args) {
Ingredient butter =new Ingredient("butter", 250, "raw", "g");
Ingredient milk =new Ingredient("milk", 1000, "raw", "ml");
System.out.println(butter);
System.out.println(milk);
}
}
The state of an ingredient can be cooked or raw and the unit either a weight unit (g, kg) or a volume unit (l, ml, cl). The state and unit are stored in lower case.
Question 1. Add a price attribute to the Ingredient class. The price should be given when creating an ingredient. Do not forget to modify the __str__ (toString() in Java) method to include the price.
Question 2. Write a Meal class that represents a meal, each meal having a name and a list of ingredients. The name of the meal should be given at creation time. The list of ingredients however, may be empty. You should also be able to add and remove an ingredient to/from a meal.
Question 3. Add a __str__ (toString() in Java) method to the Meal class that shows the name of the meal, followed by its price (the sum of the price of each ingredient) and the list of the ingredients. For example, for the pizza Margarita meal:
Question 4. Write a main method that creates a meal called pizza_margharita containing the ingredients listed in the previous question.
Print the meal to check that the __str__ method works correctly.
Question 5. We want to compare meals and therefore their ingredients. Add an __eq__ method (or boolean equals(Object) in Java) in the Ingredient class that returns true if two ingredients have the same food name and the same state (not necessarily the same quantity). Add an __eq__ method in the Meal class that returns true if two meals contain the same ingredients.
Solution to the exercise
classIngredient:
"""
Represents an ingredient with a name, quantity, state, unit and price.
"""def __init__(self, name: str, quantity: int, state: str, unit: str, price: float):
"""
Initializes a new instance of the Ingredient class.
Parameters:
-----------
name: str
The name of the ingredient.
quantity: int
The quantity of the ingredient.
state: str
The state of the ingredient (raw or cooked).
unit: str
The unit of the ingredient (g, kg, ml, cl, l).
price: float
The price of the ingredient.
Raises:
-------
ValueError: If the quantity is negative, the unit is not valid, the state is not valid or the price is negative.
"""if quantity <0:
raiseValueError("Quantity must be positive")
if unit.lower() notin ("g", "kg", "ml", "cl", "l"):
raiseValueError("Unit must be 'g','kg', 'ml', 'cl', or 'l'")
if state.lower() notin ("raw", "cooked"):
raiseValueError("State must be 'raw' or 'cooked'")
if price <0:
raiseValueError("Price must be positive")
self._name = name
self._quantity = quantity
self._unit = unit.lower()
self._state = state.lower()
self._price = price
def __str__(self) -> str:
"""
Returns a string representation of the Ingredient object.
"""returnf"{self._quantity}{self._unit}{self._name} ({self._state}, {self._price}€)"def __eq__(self, other: 'Ingredient') -> bool:
"""
Compares two Ingredient objects.
Two ingredients are equal if they have the same name and state.
Parameters:
-----------
other: Ingredient
The ingredient to compare with.
Returns:
--------
bool: True if the ingredients are equal, False otherwise.
"""return self._name == other._name and self._state == other._state
/**
* Represents an ingredient with a name, quantity, state, unit and price.
*/publicclassIngredient {
privatefinal String name;
privatefinalint quantity;
privatefinal String state;
privatefinal String unit;
privatefinalfloat price;
/**
* Initializes a new instance of the Ingredient class.
*
* @param name: the name of the ingredient
* @param quantity: the quantity of the ingredient
* @param state: the state of the ingredient (raw or cooked)
* @param unit: the unit of the ingredient (g, kg, ml, cl, l)
* @param price: the price of the ingredient
* @throws IllegalArgumentException if the quantity is negative, the unit is not valid, the state is not valid or the price is negative
*/publicIngredient(String name, int quantity, String state, String unit, float price) {
if (quantity < 0) {
thrownew IllegalArgumentException("Quantity must be positive");
}
if (!unit.toLowerCase().matches("g|kg|ml|cl|l")) {
thrownew IllegalArgumentException("Unit must be 'g','kg', 'ml', 'cl', or 'l'");
}
if (!state.toLowerCase().matches("raw|cooked")) {
thrownew IllegalArgumentException("State must be 'raw' or 'cooked'");
}
if(price < 0) {
thrownew IllegalArgumentException("Price must be positive");
}
this.name= name;
this.quantity= quantity;
this.state= state.toLowerCase();
this.unit= unit.toLowerCase();
this.price= price;
}
/**
* Returns the price of the ingredient.
* @return the price of the ingredient
*/publicfloatgetPrice() {
return price;
}
/**
* Returns a string representation of the Ingredient object.
* @return a string representation of the Ingredient object
*/@Overridepublic String toString() {
return quantity + unit +" "+ name +" ("+ state +", "+ price +"€)";
}
/**
* Compares two Ingredient objects.
* Two ingredients are equal if they have the same name and state.
*
* @param obj the ingredient to compare with
* @return true if the ingredients are equal, false otherwise
*/@Overridepublicbooleanequals(Object obj) {
if (obj instanceof Ingredient) {
Ingredient other = (Ingredient) obj;
return name.equals(other.name) && state.equals(other.state);
}
returnfalse;
}
}
classMeal:
"""
Represents a meal with a name and a list of ingredients.
"""def __init__(self, name: str, ingredients: list[Ingredient] = []) ->None:
"""
Initializes a new instance of the Meal class.
Parameters:
-----------
name: str
The name of the meal.
ingredients: List[Ingredient]
The list of ingredients of the meal, may be empty.
""" self._name: str = name
self._ingredients: list[Ingredient] = ingredients
defadd_ingredient(self, ingredient: Ingredient):
"""
Adds an ingredient to the meal.
Parameters:
-----------
ingredient: Ingredient
The ingredient to add.
""" self._ingredients.append(ingredient)
defremove_ingredient(self, ingredient: Ingredient):
"""
Removes an ingredient from the meal.
Parameters:
-----------
ingredient: Ingredient
The ingredient to remove.
""" self._ingredients.remove(ingredient)
def __str__(self) -> str:
"""
Returns a string representation of the Meal object.
""" nl ="\n- "return (
f"{self._name} - {sum(ing._price for ing in self._ingredients)}€"f"{nl}{nl.join(str(ing) for ing in self._ingredients)}" )
/**
* Represents a meal with a name and a list of ingredients.
*/publicclassMeal {
privatefinal String name;
privatefinal List<Ingredient> ingredients;
/**
* Initializes a new instance of the Meal class.
*
* @param name: the name of the meal
* @param ingredients: the list of ingredients of the meal, may be empty
*/publicMeal(String name, List<Ingredient> ingredients) {
this.name= name;
this.ingredients=new ArrayList<>(ingredients);
}
/**
* Initializes a new instance of the Meal class.
*
* @param name: the name of the meal
*/publicMeal(String name) {
this(name, new ArrayList<>());
}
/**
* Adds an ingredient to the meal.
*
* @param ingredient: the ingredient to add
*/publicvoidaddIngredient(Ingredient ingredient) {
ingredients.add(ingredient);
}
/**
* Removes an ingredient from the meal.
*
* @param ingredient: the ingredient to remove
*/publicvoidremoveIngredient(Ingredient ingredient) {
ingredients.remove(ingredient);
}
/**
* Returns a string representation of the Meal object.
* @return a string representation of the Meal object
*/@Overridepublic String toString() {
return name +" - "+ ingredients.stream().mapToDouble(Ingredient::getPrice).sum() +"€ \n"+ ingredients.stream().map(ing ->"- "+ ing +"\n").reduce("", String::concat);
}
/**
* Compares two Meal objects.
* Two meals are equal if they contain the same ingredients.
*
* @param obj the meal to compare with
* @return true if the meals are equal, false otherwise
*/@Overridepublicbooleanequals(Object obj) {
if (obj instanceof Meal) {
Meal other = (Meal) obj;
return ingredients.equals(other.ingredients);
}
returnfalse;
}
}
Anything you would have liked to see here?
Let us know on the Discord server!
Maybe we can add it quickly.
Otherwise, it will help us improve the course for next year!