Practical session

Duration of the supervised session2h30

Exercise 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.

class Blackboard:
  """ Represents a blackboard with a surface that can be written on, read and erase.
  """
  def __init__(self):
    """
    Initializes a new instance of the Blackboard class.
    """
    self.__surface = ""

  def write(self, text: str):
    """
    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

  def read(self) -> str:
    """
    Returns the text written on the surface of the blackboard.
    """
    return self.__surface

  def show(self):
    """
    Shows the text written on the surface of the blackboard.
    """
    print(self.__surface)

  def erase(self):
    """
    Erases the text written on the surface of the blackboard.
    """
    if self.__surface != "":
        self.__surface = ""

  def get_surface(self):
    return self.__surface

In the solution, we choose to make the attribute surface private so that direct modification of the content is not possible.

/**
 * Represents a blackboard with a surface that can be written on, read and erase.
 */
public class Blackboard {
    // The surface of the blackboard, as a private attribute
    private String surface;

    /**
     * Initializes a new instance of the Blackboard class.
     */
    public Blackboard() {
        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
     */
    public void write(String text) {
        if (!text.isEmpty()) {
            // Add a new line if the text is not empty
            this.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() {
        return this.surface;
    }

    /**
     * Shows the text written on the surface of the blackboard.
     */
    public void show() {
        System.out.println(this.surface);
    }

    /**
     * Erases the text written on the surface of the blackboard.
     */
    public void erase() {
        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")
public static void main(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");
    }
}

Exercise 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 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 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
from typing import *

class BankAccount:
    """
    Represents a bank account with a balance and a transaction history.
    """
    def __init__(self, account_number: int):
        """
        Initializes a new instance of the BankAccount class.

        Parameters:
        -----------
        account_number: int
            The account number.
        """
        self.__account_number = account_number
        self.__balance = 0.0
        self.__history = []

    def deposit(self, amount: float):
        """
        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:
            raise ValueError("Amount must be positive")
        else:
            self.__balance += amount
            self.__history.append(amount)

    def withdraw(self, amount: float):
        """
        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:
            raise ValueError("Amount must be positive")
        elif amount > self.__balance:
            raise ValueError("Amount must be less than the balance")
        else:
            self.__balance -= amount
            self.__history.append(-amount)

    def get_account_number(self) -> int:
        """
        Returns the account number of the bank account.
        """
        return self.__account_number

    def get_balance(self) -> float:
        """
        Returns the balance of the bank account.

        Returns:
        --------
        The balance of the bank account.
        """
        return self.__balance

    def get_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.
 */
public class BankAccount {
    private final int accountNumber; // final keyword makes the attribute immutable
    private float balance;
    private final ArrayList<Float> history;

    /**
     * Initializes a new instance of the BankAccount class.
     * @param accountNumber: the account number
     */
    public BankAccount(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
     */
    public void deposit(float amount) {
        if (amount <= 0) {
            throw new 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
     */
    public void withdraw(float amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("Amount must be positive");
        }
        if (this.balance < amount) {
            throw new IllegalArgumentException("Insufficient funds");
        }
        this.balance -= amount;
        this.history.add(-amount);
    }

    /**
     * Returns the account number of the bank account.
     * @return the account number
     */
    public int getAccountNumber() {
        return this.accountNumber;
    }

    /**
     * Returns the balance of the bank account.
     * @return the balance
     */
    public float getBalance() {
        return this.balance;
    }

    /**
     * Returns the transaction history of the bank account.
     * @return the transaction history
     */
    public ArrayList<Float> getTransactionHistory() {
        return this.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)
    except ValueError as e:
        print("ERROR", e)
    try:
        my_account.withdraw(10000)
    except ValueError as e:
        print("ERROR", e)
    try:
        my_account.withdraw(-50)
    except ValueError as e:
        print("ERROR", e)

    my_account.withdraw(100)
    print("Transaction history:", my_account.get_transaction_history()) # Should give [500, 200, -50, 100]
    print("Balance:", my_account.get_balance()) # Should give 300
/**
 * Represents a bank account with a balance and a transaction history.
 */
public static void main(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, -50, 100]
        System.out.println("Transaction history: " + myAccount.getBalance()); // Should give 300
    } catch (Exception e) {
        System.out.println("ERROR " + e);
    }
}

Exercise 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 that contains who holds the account. The account holder must be specified when the account is created (otherwise an exception 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.

  1. 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.

  • A method get_owner(self) -> Person 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.

  1. 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) method adds account to the list of bank accounts of the owner only if it is not already in the list. An exception ValueError is raised if account is None.

  • The remove_account(self, account: BankAccount) 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.

  1. 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. Use the print() method to be sure that your code works. Verify that exceptions are raised as expected.
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.

You can find some information on this in the documentation page.

  1. BankAccount class modifications.
  • 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 typing import *

from person import Person

class BankAccount:
    """
    Represents a bank account with a number, an owner, a balance, and a transaction history.
    """
    def __init__(self, account_number: int, owner: Person):
        """
        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 is None:
            raise ValueError("Owner must be specified")
        self.__account_number = account_number
        self.__owner = owner
        self.__balance = 0
        self.__history = []
        owner.add_account(self)

    def deposit(self, amount: float):
        """
        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:
            raise ValueError("Amount must be positive")
        self.__balance += amount
        self.__history.append(amount)

    def withdraw(self, amount: float):
        """
        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.
        """
        if 0 < amount <= self.__balance:
          self.__balance -= amount
          self.__history.append(-amount)
        else:
          raise ValueError("Amount must be positive and less than the balance")

    def get_account_number(self) -> int:
        """
        Returns the account number of the bank account.
        """
        return self.__account_number

    def get_balance(self) -> float:
        """
        Returns the balance of the bank account.

        Returns:
        --------
        The balance of the bank account.
        """
        return self.__balance

    def get_transaction_history(self) -> List[float]:
        """
        Returns the transaction history of the bank account.

        Returns:
        --------
        The transaction history of the bank account.
        """
        return self.__history

    def get_owner(self) -> Person:
        """
        Returns the owner of the bank account.

        Returns:
        --------
        The owner of the bank account.
        """
        return self.__owner

    def __str__(self) -> str:
        return f"Account number {self.__account_number} from {self.__owner.fullname()} has {self.__balance} € and has done {len(self.__history)} transactions"

    def __repr__(self) -> str:
        return f"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.
 */
public class BankAccount {
    private final Person owner;
    private final int accountNumber;
    private float balance;
    private final ArrayList<Float> history;

    /**
     * Initializes a new instance of the BankAccount class.
     *
     * @param accountNumber: the account number
     */
    public BankAccount(int accountNumber, Person owner) {
        if (owner == null) {
            throw new 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
     */
    public void deposit(float amount) {
        if (amount <= 0) {
            throw new 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
     */
    public void withdraw(float amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("Amount must be positive");
        }
        if (this.balance < amount) {
            throw new IllegalArgumentException("Insufficient funds");
        }
        this.balance -= amount;
        this.history.add(-amount);
    }

    /**
     * Returns the account number of the bank account.
     *
     * @return the account number
     */
    public int getAccountNumber() {
        return this.accountNumber;
    }

    /**
     * Returns the balance of the bank account.
     *
     * @return the balance
     */
    public float getBalance() {
        return this.balance;
    }

    /**
     * Returns the transaction history of the bank account.
     *
     * @return the transaction history
     */
    public ArrayList<Float> getTransactionHistory() {
        return this.history;
    }

    /**
     * Returns the owner of the bank account.
     *
     * @return the owner
     */
    public Person getOwner() {
        return this.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.
    @Override
    public 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

class Person:
    """ Represents a person with a name, firstname, age and the list of bank accounts.
    """
    def __init__(self, name: str, firstname: str, age: int): # The constructor
        """ Constructs a new Person object with the specified lastname, firstname, age, and his/her accounts.
        """
        self.__lastname = name.upper();
        self.__firstname = firstname
        self.__age = age
        self.__accounts = []

    def fullname(self) -> str:
        return f"{self.__firstname} {self.__lastname}"

    def get_accounts(self) -> List[BankAccount]:
        """ Returns the list of bank accounts of the person.
        """
        return self.__accounts

    def add_account(self, account: BankAccount):
        """ 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.
        Raises:
            ValueError: If the specified bank account is `None`.
        """
        if account is None:
            raise ValueError("Account cannot be None")

        if account not in self.__accounts:
            self.__accounts.append(account)

    def remove_account(self, account: BankAccount):
        """ 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.
        """
        return f"{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.
        """
        if not isinstance(other, Person):
            return False
        return 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.
 */
public class Person {

    private final String firstName;
    private final String lastName;
    private int age; // not final because it can change
    private final 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
     */
    public Person(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() {
        return this.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
     * @throws IllegalArgumentException if the account is null
     */
    public void addAccount(BankAccount account) {
        if (account == null) {
            throw new IllegalArgumentException("Account cannot be null");
        }
        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
     */
    public void removeAccount(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.
    @Override
    public 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.
     */
    @Override
    public boolean equals(Object obj) {
        // First, check if the object given is an instance of Person
        if (obj instanceof Person) {
            // If it is, cast it to a Person object
            Person person = (Person) obj;
            // Then, compare the fields of the two objects
            return this.firstName.equals(person.firstName) && this.lastName.equals(person.lastName) && this.age == person.age;
        }
        return false;
    }
}
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 a bank account for Alice
    alice_account1 = BankAccount(1, alice_weber)
    try:
        alice_weber.add_account(None)
    except ValueError:
        print("ERROR. No added account. Account cannot be None")

    # Create another bank account for Alice
    alice_account2 = BankAccount(2, alice_weber)
    print(alice_weber) # Should give two bank accounts

    # Remove the second bank account from Alice's accounts
    alice_weber.remove_account(alice_account2)
    print(alice_weber) # Should give one bank account

    # Create a third bank account for Alice
    alice_account3 = BankAccount(3, alice_weber)

    # Test __eq__ method
    new_alice = Person("Weber", "Alice", 33)
    if alice_account1.get_owner() == new_alice:
        print("get_owner Success")
    else:
        print("get_owner Failure")

    if alice_account1.get_balance() == 0:
        print("get_balance Success")
    else:
        print("get_balance Failure")

    if alice_account1.get_transaction_history() == []:
        print("get_transaction_history Success")
    else:
        print("get_transaction_history Failure")

    # Add some transactions to alice_account1
    alice_account1.deposit(500)
    alice_account1.withdraw(200)

    print(alice_account1.get_balance()) ## Should give 300
    print(alice_account1.get_transaction_history()) ## Should give [500, -200]

    str_elements = [str(element) for element in alice_weber.get_accounts()]
    print(str_elements) ## Should give [alice_account1, alice_account3]

    print(alice_weber.get_accounts()) ## Should give [alice_account1, alice_account3] if __repr__ defined in BankAccount

    print(alice_account1.get_owner())
public static void main(String[] args) {
    // Create a person
    Person alice_weber = new Person("Weber", "Alice", 33);
    System.out.println(alice_weber); // Should give no bank account

    // Create a bank account for Alice
    BankAccount alice_account1 = new BankAccount(1, alice_weber);
    try {
        alice_weber.addAccount(null);
    } catch (IllegalArgumentException e) {
        System.out.println("ERROR. No added account. Account cannot be None");
    }

    // Create another bank account for Alice
    BankAccount alice_account2 = new BankAccount(2, alice_weber);
    System.out.println(alice_weber); // Should give two bank accounts

    // Remove the second bank account from Alice's accounts
    alice_weber.removeAccount(alice_account2);
    System.out.println(alice_weber); // Should give one bank account

    // Create a third bank account for Alice
    BankAccount alice_account3 = new BankAccount(3, alice_weber);

    // 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");
    }

    if (alice_account1.getBalance() == 0) {
        System.out.println("getBalance Success");
    } else {
        System.out.println("getBalance Failure");
    }

    if (alice_account1.getTransactionHistory().isEmpty()) {
        System.out.println("getTransactionHistory Success");
    } else {
        System.out.println("getTransactionHistory Failure");
    }

    //  Add some transactions to alice_account1
    alice_account1.deposit(500);
    alice_account1.withdraw(200);

    System.out.println(alice_account1.getBalance()); // Should give 300.0
    System.out.println(alice_account1.getTransactionHistory()); // Should give [500.0, -200.0]

    ArrayList<BankAccount> elements = alice_weber.getAccounts();
    System.out.println(elements); // Should give [alice_account1, alice_account3]

    System.out.println(alice_account1.getOwner());
}

Exercise 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 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) (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) (or void fireAssistant(Person assistant) in Java) that removes assistant from the list of assistants if it exists
    • get_assistants(self) (or getAssistants() in Java) that returns the list of assistants
    • spend_allocation(self, amount: float) (or void spendAllocation(float amount) in Java) that distributes amount equally between the assistants if the amount is positive

Question. Given the provided classes Person, BankAccount, create the class ElectedOfficial as described above. In order to implement this class, you’ll need to modify the given Person class by adding a method (e.g., add_budget(amount: float) (or void addBudget(float amount) in Java)) that allows you to add money to one of the person’s bank accounts (e.g., the first in the list).

Optional. 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 typing import *

from person import Person

class ElectedOfficial(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): # 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 = []

    def hire_assistant(self, assistant: Person):
        """
        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 is not None:
            self.__assistants.append(assistant)

    def fire_assistant(self, assistant: Person):
        """
        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)

    def get_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

    def spend_allocation(self, amount: float):
        """
        Distributes the specified amount equally between the assistants of the elected official if the amount is positive.

        Parameters:
        -----------
        amount: float
            The amount to distribute.
        """
        if amount > 0:
            for assistant in self.__assistants:
                assistant.add_budget(amount / len(self.__assistants))

    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" with {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.
 */
public class ElectedOfficial extends Person {

    // The list of assistants of the elected official
    private final 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
     */
    public ElectedOfficial(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
     */
    public void hireAssistant(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
     */
    public void fireAssistant(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
     */
    public void spendAllocation(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
     */
    @Override
    public String toString() {
        return super.toString() + " with " + assistants.size() + " assistants";
    }
}

Question. Now we want to represent the thrifty elected representatives, who divide up their budget allocation as follows: each assistant receives no more than the minimum wage (1766.92 euros gross), and, if there’s any money left over, it’s not spent. Write a class representing the thrifty elected representatives (ThriftyElectedRepresentative).

Solution to the exercise

Implementation bellow assumes that both ElectedOfficial and ThriftyElectedRepresentative classes are in the same file (e.g., representatives.py). The min_wage variable is declared as a global variable in the class.

from person import Person

class ThriftyElectedRepresentative(ElectedOfficial):
    """ Represents a thrifty elected representative.
    Each assistant receives no more than the minimum wage (1766.92 euros gross), and, if there's any money left over, it's not spent.
    """

    min_wage= 1766.92 # Minimum wage in euros

    def __init__(self, name: str, firstname: str, age: int): # The constructor
        """ Constructs a new ThriftyElectedRepresentative object with the specified lastname, firstname, age.
        """
        super().__init__(name, firstname, age) # Call the constructor of the parent class

    def spend_allocation(self, amount: float): # Redefine the parent method
        """
        Distributes the specified amount of money to the assistants.
        Each assistant receives no more than the minimum wage (1766.92 euros gross).
        If there's any money left over, it's not spent.
        """
        if amount > ThriftyElectedRepresentative.min_wage:
            for assistant in super().get_assistants():
                assistant.add_budget(ThriftyElectedRepresentative.min_wage)
                amount -= ThriftyElectedRepresentative.min_wage
        if amount > 0:
            super().add_budget(amount)
/**
 * Represents a thrifty elected representative.
 * Each assistant receives no more than the minimum wage (1766.92 euros gross), and, if there's any money left over, it's not spent.
 */
public class ThriftyElectedRepresentative extends ElectedOfficial {

    // Minimum wage in euros
    // The variable is declared as a static final variable to be shared among all instances of the class
    private static final float MINIMUM_WAGE = 1766.92f;

    /**
     * Constructs a new ThriftyElectedRepresentative object with the specified lastname, firstname, age.
     *
     * @param lastName  the last name
     * @param firstName the first name
     * @param age       the age
     */
    public ThriftyElectedRepresentative(String lastName, String firstName, int age) {
        super(lastName, firstName, age);
    }

    /**
     * Distributes the specified amount of money to the assistants.
     * Each assistant receives no more than the minimum wage (1766.92 euros gross).
     * If there's any money left over, it's not spent.
     *
     * @param amount the amount of money to distribute
     */
    @Override
    public void spendAllocation(float amount) {
        if (amount > MINIMUM_WAGE) {
            for (Person assistant : getAssistants()) {
                assistant.addBudget(MINIMUM_WAGE);
                amount -= MINIMUM_WAGE;
            }
        } else if (amount > 0) {
            super.addBudget(amount);
        }
    }
}

To go further

Exercise 5 - 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 initialize attributes (size, opened, placed_in, and content)
  • open(): opens the doll if it is not already open 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
class RussianDoll:
    """
    Represents a Russian doll with a size, a state (opened or closed), a content and a container.
    """

    def __init__(self, size: int):
        """
        Initializes a new instance of the RussianDoll class.

        Parameters:
        -----------
        size: int
            The size of the doll.
        """
        self.__size = size
        self.__opened = False
        self.__content = None
        self.__placed_in = None

    def open(self):
        """
        Opens the doll if it is not already open and if it is not inside another doll.
        """
        if not self.__opened and self.__placed_in is None:
            self.__opened = True

    def close(self):
        """
        Closes the doll if it is not already closed and if it is not inside another doll.
        """
        if self.__opened and self.__placed_in is None:
            self.__opened = False

    def place_in(self, p: RussianDoll):
        """
        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 is None and p.__opened and p.__content is None and p.__size > self.__size:
            self.__placed_in = p
            p.__content = self

    def get_out(self, p: RussianDoll):
        """
        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 = None
/**
 * Represents a Russian doll with a size, a state (opened or closed), a content and a container.
 */
public class RussianDoll{

    private final int size;
    private boolean opened;
    private RussianDoll content;
    private RussianDoll placedIn;

    /**
     * Initializes a new instance of the RussianDoll class.
     *
     * @param size the size of the doll
     */
    public RussianDoll(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.
     */
    public void open() {
        if (!opened && placedIn == null) {
            opened = true;
        }
    }

    /**
     * Closes the doll if it is not already closed and if it is not inside another doll.
     */
    public void close() {
        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
     */
    public void placeIn(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
     */
    public void getOut(RussianDoll p) {
        if (placedIn == p && p.opened) {
            placedIn = null;
            p.content = null;
        }
    }
}

Exercice 6 - Meals

We want to develop a recipe management program for a restaurant. A programmer has already written the Ingredient class given below:

class Ingredient:
    """
    Represents an ingredient with a name, quantity, state and unit.
    """
    def __init__(self, name: str, quantity: int, state: str, unit: str):
        """
        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:
            raise ValueError("Quantity must be positive")
        if unit.lower() not in ("g", "kg", "ml", "cl", "l"):
            raise ValueError("Unit must be 'g','kg', 'ml', 'cl', or 'l'")
        if state.lower() not in ("raw", "cooked"):
            raise ValueError("State must be 'raw' or 'cooked'")

        self.__name = name
        self.__quantity = quantity
        self.__unit = unit.lower()
        self.__state = state.lower()

    def __str__(self) -> str:
        """
        Returns a string representation of the Ingredient object.
        """
        return f"{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.
 */
public class Ingredient {
    private final String name;
    private final int quantity;
    private final String state;
    private final 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
     */
    public Ingredient(String name, int quantity, String state, String unit) {
        if (quantity < 0) {
            throw new IllegalArgumentException("Quantity must be positive");
        }
        if (!unit.toLowerCase().matches("g|kg|ml|cl|l")) {
            throw new IllegalArgumentException("Unit must be 'g','kg', 'ml', 'cl', or 'l'");
        }
        if (!state.toLowerCase().matches("raw|cooked")) {
            throw new 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
     */
    @Override
    public String toString() {
        return quantity + unit + " " + name + " (" + state + ")";
    }

    public static void main(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:

Pizza Margherita - 7.84€
- 260g Pizza Dough (raw, 1.15€),
- 200g Tomato Sauce (cooked, 2.27€),
- 200g Mozzarella (raw, 1.19€),
- 60g Parmigiano Reggiano (raw, 1.83€),
- 30g Basil (raw, 1.4€)

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
class Ingredient:
    """
    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:
            raise ValueError("Quantity must be positive")
        if unit.lower() not in ("g", "kg", "ml", "cl", "l"):
            raise ValueError("Unit must be 'g','kg', 'ml', 'cl', or 'l'")
        if state.lower() not in ("raw", "cooked"):
            raise ValueError("State must be 'raw' or 'cooked'")
        if price < 0:
            raise ValueError("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.
        """
        return f"{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.
 */
public class Ingredient {
    private final String name;
    private final int quantity;
    private final String state;
    private final String unit;
    private final float 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
     */
    public Ingredient(String name, int quantity, String state, String unit, float price) {
        if (quantity < 0) {
            throw new IllegalArgumentException("Quantity must be positive");
        }
        if (!unit.toLowerCase().matches("g|kg|ml|cl|l")) {
            throw new IllegalArgumentException("Unit must be 'g','kg', 'ml', 'cl', or 'l'");
        }
        if (!state.toLowerCase().matches("raw|cooked")) {
            throw new IllegalArgumentException("State must be 'raw' or 'cooked'");
        }
        if(price < 0) {
            throw new 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
     */
    public float getPrice() {
        return price;
    }

    /**
     * Returns a string representation of the Ingredient object.
     * @return a string representation of the Ingredient object
     */
    @Override
    public 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
     */
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Ingredient) {
            Ingredient other = (Ingredient) obj;
            return name.equals(other.name) && state.equals(other.state);
        }
        return false;
    }
}
from typing import List

class Meal:
    """
    Represents a meal with a name and a list of ingredients.
    """

    def __init__(self, name: str, ingredients: List[Ingredient] = []):
        """
        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 = name
        self.__ingredients = []

    def add_ingredient(self, ingredient: Ingredient):
        """
        Adds an ingredient to the meal.

        Parameters:
        -----------
        ingredient: Ingredient
            The ingredient to add.
        """
        self.__ingredients.append(ingredient)

    def remove_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._Ingredient__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.
 */
public class Meal {
    private final String name;
    private final 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
     */
    public Meal(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
     */
    public Meal(String name) {
        this(name, new ArrayList<>());
    }

    /**
     * Adds an ingredient to the meal.
     *
     * @param ingredient: the ingredient to add
     */
    public void addIngredient(Ingredient ingredient) {
        ingredients.add(ingredient);
    }

    /**
     * Removes an ingredient from the meal.
     *
     * @param ingredient: the ingredient to remove
     */
    public void removeIngredient(Ingredient ingredient) {
        ingredients.remove(ingredient);
    }

    /**
     * Returns a string representation of the Meal object.
     * @return a string representation of the Meal object
     */
    @Override
    public 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
     */
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Meal) {
            Meal other = (Meal) obj;
            return ingredients.equals(other.ingredients);
        }
        return false;
    }
}
if __name__ == "__main__":
    # Pizza Margherita
    pizza_dough = Ingredient("Pizza Dough", 260, "raw", "g", 1.15)
    tomato_sauce = Ingredient("Tomato Sauce", 200, "cooked", "g", 2.27)
    mozzarella = Ingredient("Mozzarella", 125, "raw", "g", 1.19)
    parmigiano = Ingredient("Parmigiano Reggiano", 60, "raw", "g", 1.83)
    basil = Ingredient("Fresh Basil", 30, "raw", "g", 1.4)

    pizza = Meal("Pizza Margherita")
    pizza.add_ingredient(pizza_dough)
    pizza.add_ingredient(tomato_sauce)
    pizza.add_ingredient(mozzarella)
    pizza.add_ingredient(parmigiano)
    pizza.add_ingredient(basil)

    print(pizza)
public static void main(String[] args) {
    // Pizza Margherita
    Ingredient pizzaDough = new Ingredient("Pizza Dough", 260, "raw", "g", 1.15f);

    Ingredient tomatoSauce = new Ingredient("Tomato Sauce", 200, "cooked", "g", 2.27f);
    Ingredient mozzarella = new Ingredient("Mozzarella", 125, "raw", "g", 1.19f);
    Ingredient parmigiano = new Ingredient("Parmigiano Reggiano", 60, "raw", "g", 1.83f);
    Ingredient basil = new Ingredient("Fresh Basil", 30, "raw", "g", 1.4f);

    Meal pizza = new Meal("Pizza Margherita");
    pizza.addIngredient(dough);
    pizza.addIngredient(tomatoSauce);
    pizza.addIngredient(mozzarella);
    pizza.addIngredient(parmigiano);
    pizza.addIngredient(basil);

    System.out.println(pizza);
}