From cf12c0ade201d1e48a951ca18cf7d406ca2c566b Mon Sep 17 00:00:00 2001 From: Ammad Date: Sun, 28 Jun 2026 20:54:48 +0100 Subject: [PATCH 1/9] Completed "Why we use types" exercises. --- prep exercises/Why we use types exercise 1.py | 9 +++++++++ prep exercises/Why we use types exercise 2.py | 15 +++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 prep exercises/Why we use types exercise 1.py create mode 100644 prep exercises/Why we use types exercise 2.py diff --git a/prep exercises/Why we use types exercise 1.py b/prep exercises/Why we use types exercise 1.py new file mode 100644 index 000000000..50920fde2 --- /dev/null +++ b/prep exercises/Why we use types exercise 1.py @@ -0,0 +1,9 @@ +def double(value): + return value * 2 + +# I predict that double("22") will produce 22.0 + +result = double("22") +print(result) # Actual result is 2222 + +# This is because the string is duplicated twice, "22" + "22" = "22" * 2, it is repeating the a sequence of string. diff --git a/prep exercises/Why we use types exercise 2.py b/prep exercises/Why we use types exercise 2.py new file mode 100644 index 000000000..835d5f428 --- /dev/null +++ b/prep exercises/Why we use types exercise 2.py @@ -0,0 +1,15 @@ +def double(number): + return number * 3 + +print(double(10)) + +# There are two possible ways to fix the bug. + +# 1. The function name is wrong, it needs to be named to triple +# 2. Or the function name is correct but the calculation is wrong, it should be return * 2 + +def double(number): + return number * 2 + +value = 10 +print(double(value)) From d1f8599897c7bd5187aaf90b433a2952e2742e17 Mon Sep 17 00:00:00 2001 From: Ammad Date: Sun, 28 Jun 2026 21:17:30 +0100 Subject: [PATCH 2/9] Completed "Type checking with mypy" exercise. --- .../Type checking with mypy exercise.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 prep exercises/Type checking with mypy exercise.py diff --git a/prep exercises/Type checking with mypy exercise.py b/prep exercises/Type checking with mypy exercise.py new file mode 100644 index 000000000..ff07b0603 --- /dev/null +++ b/prep exercises/Type checking with mypy exercise.py @@ -0,0 +1,34 @@ +def open_account(balances: dict[str, float], name: str, amount: float) -> None: + balances[name] = amount + +def sum_balances(accounts: dict[str, float]) -> float: + total: float = 0 + + for name, pence in accounts.items(): + print(f"{name} had balance {pence}") + total += pence + + return total + +def format_pence_as_string(total_pence: float) -> str: + if total_pence < 100: + return f"{total_pence}p" + + pounds = int(total_pence / 100) + pence = int(total_pence % 100) + + return f"£{pounds}.{pence:02d}" + +balances: dict[str, float] = { + "Sima": 700, + "Linn": 545, + "Georg": 831, +} + +open_account(balances, "Tobi", 9.13) +open_account(balances, "Olya", 7.13) + +total_pence = sum_balances(balances) +total_string = format_pence_as_string(total_pence) + +print(f"The bank accounts total {total_string}") From 23329bfa9c38db7cf501eed8c2db9e259192351c Mon Sep 17 00:00:00 2001 From: Ammad Date: Sun, 28 Jun 2026 21:23:51 +0100 Subject: [PATCH 3/9] Completed "Classes and objects" exercises. --- .../Classes and objects exercise 1.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 prep exercises/Classes and objects exercise 1.py diff --git a/prep exercises/Classes and objects exercise 1.py b/prep exercises/Classes and objects exercise 1.py new file mode 100644 index 000000000..34624f18e --- /dev/null +++ b/prep exercises/Classes and objects exercise 1.py @@ -0,0 +1,24 @@ +class Person: + def __init__(self, name: str, age: int, preferred_operating_system: str): + self.name = name + self.age = age + self.preferred_operating_system = preferred_operating_system + +imran = Person("Imran", 22, "Ubuntu") +print(imran.name) +# print(imran.address) # error: "Person" has no attribute "address" [attr-defined] + +eliza = Person("Eliza", 34, "Arch Linux") +print(eliza.name) +# print(eliza.address) # error: "Person" has no attribute "address" [attr-defined] + +def is_adult(person: Person) -> bool: + return person.age >= 18 + +print(is_adult(imran)) + +def no_attribute_exists(person: Person): + print(person.dob) + +# mypy detects that dob attribute doesn't exist. +# error: "Person" has no attribute "dob" [attr-defined] From fbb38254f162969bce419ae1484fa1f679b29226 Mon Sep 17 00:00:00 2001 From: Ammad Date: Sun, 28 Jun 2026 21:56:23 +0100 Subject: [PATCH 4/9] Completed "Methods" exercises. --- prep exercises/Methods exercise 1 | 4 ++++ prep exercises/Methods exercise 2.py | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 prep exercises/Methods exercise 1 create mode 100644 prep exercises/Methods exercise 2.py diff --git a/prep exercises/Methods exercise 1 b/prep exercises/Methods exercise 1 new file mode 100644 index 000000000..2cdb191d6 --- /dev/null +++ b/prep exercises/Methods exercise 1 @@ -0,0 +1,4 @@ +The advantage of a method as a opposed to freestanding function is that it works on the instance of a type. +The freestanding function knows nothing about the instance of the type because it doesn't belong to a type. +On an instance, the method can influence the internal states. +However freestanding function can only work on public APIs whereas instance methods have access to internal APIs. diff --git a/prep exercises/Methods exercise 2.py b/prep exercises/Methods exercise 2.py new file mode 100644 index 000000000..fbf8af133 --- /dev/null +++ b/prep exercises/Methods exercise 2.py @@ -0,0 +1,19 @@ +from datetime import date + +class Person: + def __init__(self, name: str, date_of_birth: date, preferred_operating_system: str): + self.name = name + self.date_of_birth = date_of_birth + self.preferred_operating_system = preferred_operating_system + + def is_adult(self): + today = date.today() + age = today.year - self.date_of_birth.year + + if (today.month, today.day) < (self.date_of_birth.month, self.date_of_birth.day): + age -= 1 + + return age >= 18 + +imran = Person("Imran", date(2004, 6, 28), "Ubuntu") +print(imran.is_adult()) From 761797a4d299c9f0606acd722e221b07c096b584 Mon Sep 17 00:00:00 2001 From: Ammad Date: Sun, 28 Jun 2026 21:58:25 +0100 Subject: [PATCH 5/9] Compelted "Dataclasses" exercise. --- prep exercises/Dataclasses exercise.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 prep exercises/Dataclasses exercise.py diff --git a/prep exercises/Dataclasses exercise.py b/prep exercises/Dataclasses exercise.py new file mode 100644 index 000000000..e0366271d --- /dev/null +++ b/prep exercises/Dataclasses exercise.py @@ -0,0 +1,23 @@ +from dataclasses import dataclass +from datetime import date + +@dataclass(frozen=True) +class Person: + name: str + date_of_birth: date + preferred_operating_system: str + + def is_adult(self): + today = date.today() + age = today.year - self.date_of_birth.year + + if (today.month, today.day) < (self.date_of_birth.month, self.date_of_birth.day): + age -= 1 + + return age >= 18 + +imran = Person("Imran", date(2004, 6, 28), "Ubuntu") # We can call this constructor - @dataclass generated it for us. +print(imran) # Prints Person(name='Imran', age=22, preferred_operating_system='Ubuntu') + +imran2 = Person("Imran", date(2004, 6, 28), "Ubuntu") +print(imran == imran2) # Prints True From 06678319921a8bc6a833befb798b5c921916b892 Mon Sep 17 00:00:00 2001 From: Ammad Date: Sun, 28 Jun 2026 22:04:17 +0100 Subject: [PATCH 6/9] Completed "Generics" exercise. --- prep exercises/Generics exercise.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 prep exercises/Generics exercise.py diff --git a/prep exercises/Generics exercise.py b/prep exercises/Generics exercise.py new file mode 100644 index 000000000..0226c96fd --- /dev/null +++ b/prep exercises/Generics exercise.py @@ -0,0 +1,20 @@ +from dataclasses import dataclass +from typing import List + +@dataclass(frozen=True) +class Person: + name: str + age: int + children: List["Person"] + +fatma = Person(name="Fatma", age=7, children=[]) +aisha = Person(name="Aisha", age=11, children=[]) + +imran = Person(name="Imran", age=32, children=[fatma, aisha]) + +def print_family_tree(person: Person) -> None: + print(person.name) + for child in person.children: + print(f"- {child.name} ({child.age})") + +print_family_tree(imran) From d28322792bf12d7fa315be77e646d495c0e43f18 Mon Sep 17 00:00:00 2001 From: Ammad Date: Sun, 28 Jun 2026 22:12:07 +0100 Subject: [PATCH 7/9] Completed "Type-guided refactorings" exercise. --- .../Type-guided refactorings exercise.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 prep exercises/Type-guided refactorings exercise.py diff --git a/prep exercises/Type-guided refactorings exercise.py b/prep exercises/Type-guided refactorings exercise.py new file mode 100644 index 000000000..962cde09d --- /dev/null +++ b/prep exercises/Type-guided refactorings exercise.py @@ -0,0 +1,42 @@ +from dataclasses import dataclass +from typing import List + +@dataclass(frozen=True) +class Person: + name: str + age: int + preferred_operating_systems: List[str] + + +@dataclass(frozen=True) +class Laptop: + id: int + manufacturer: str + model: str + screen_size_in_inches: float + operating_system: str + + +def find_possible_laptops(laptops: List[Laptop], person: Person) -> List[Laptop]: + possible_laptops = [] + for laptop in laptops: + if laptop.operating_system in person.preferred_operating_systems: + possible_laptops.append(laptop) + return possible_laptops + + +people = [ + Person(name="Imran", age=22, preferred_operating_systems=["Ubuntu"]), + Person(name="Eliza", age=34, preferred_operating_systems=["Arch Linux"]), +] + +laptops = [ + Laptop(id=1, manufacturer="Dell", model="XPS", screen_size_in_inches=13, operating_system="Arch Linux"), + Laptop(id=2, manufacturer="Dell", model="XPS", screen_size_in_inches=15, operating_system="Ubuntu"), + Laptop(id=3, manufacturer="Dell", model="XPS", screen_size_in_inches=15, operating_system="ubuntu"), + Laptop(id=4, manufacturer="Apple", model="macBook", screen_size_in_inches=13, operating_system="macOS"), +] + +for person in people: + possible_laptops = find_possible_laptops(laptops, person) + print(f"Possible laptops for {person.name}: {possible_laptops}") From a8204fe7dae9871c2c6a23dd60489ab67a9645f6 Mon Sep 17 00:00:00 2001 From: Ammad Date: Sun, 28 Jun 2026 23:31:13 +0100 Subject: [PATCH 8/9] Completed Enums exercise. --- prep exercises/Enums exercise.py | 109 +++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 prep exercises/Enums exercise.py diff --git a/prep exercises/Enums exercise.py b/prep exercises/Enums exercise.py new file mode 100644 index 000000000..54c88261a --- /dev/null +++ b/prep exercises/Enums exercise.py @@ -0,0 +1,109 @@ +from dataclasses import dataclass +from enum import Enum +from typing import List +import sys + +class OperatingSystem(Enum): + MACOS = "macOS" + ARCH = "Arch Linux" + UBUNTU = "Ubuntu" + +@dataclass(frozen=True) +class Person: + name: str + age: int + preferred_operating_system: OperatingSystem + + +@dataclass(frozen=True) +class Laptop: + id: int + manufacturer: str + model: str + screen_size_in_inches: float + operating_system: OperatingSystem + + +def find_possible_laptops(laptops: List[Laptop], person: Person) -> List[Laptop]: + possible_laptops = [] + for laptop in laptops: + if laptop.operating_system == person.preferred_operating_system: + possible_laptops.append(laptop) + return possible_laptops + + +people = [ + Person(name="Imran", age=22, preferred_operating_system=OperatingSystem.UBUNTU), + Person(name="Eliza", age=34, preferred_operating_system=OperatingSystem.ARCH), +] + +laptops = [ + Laptop(id=1, manufacturer="Dell", model="XPS", screen_size_in_inches=13, operating_system=OperatingSystem.ARCH), + Laptop(id=2, manufacturer="Dell", model="XPS", screen_size_in_inches=15, operating_system=OperatingSystem.UBUNTU), + Laptop(id=3, manufacturer="Dell", model="XPS", screen_size_in_inches=15, operating_system=OperatingSystem.UBUNTU), + Laptop(id=4, manufacturer="Apple", model="macBook", screen_size_in_inches=13, operating_system=OperatingSystem.MACOS), +] + +# for person in people: +# possible_laptops = find_possible_laptops(laptops, person) +# print(f"Possible laptops for {person.name}: {possible_laptops}") + +name = input("Please enter your name: ") + +def get_age() -> int: + while True: + try: + age_string = input("Please enter your age, you must be >= 18 years old: ") + age = int(age_string) + + if age < 18: + print("You must be 18+.") + continue + + return age + except ValueError: + print("Please enter a valid number.") + +age = get_age() + +def get_preferred_operating_system() -> OperatingSystem: + while True: + preferred_operating_system_input = input("Your preferred operating system (macOS, Ubuntu or Arch Linux): ") + + try: + normalised_value = preferred_operating_system_input.strip() + operating_system = OperatingSystem(normalised_value) + return operating_system + except ValueError: + print(f"Sorry, we don't have {preferred_operating_system_input}. Please choose from macOS, Ubuntu or Arch Linux.") + +operating_system = get_preferred_operating_system() + +person = Person(name=name, age=age, preferred_operating_system=operating_system) +people.append(person) + +def find_laptop() -> None: + matching_laptops = [] + non_matching_laptops = [] + + for laptop in laptops: + if laptop.operating_system == person.preferred_operating_system: + matching_laptops.append(laptop) + else: + non_matching_laptops.append(laptop) + + if matching_laptops: + count = len(matching_laptops) + print(f"Congratulations, we have found {count} matching laptops!") + + other_laptops_count = len(non_matching_laptops) + + if other_laptops_count > count: + print(f"We have found {other_laptops_count} laptops with other operating systems. You might be interested in switching to a different operating system.") + + sys.exit(0) + else: + sys.exit("Sorry, we couldn't find any matching laptops!") + + +find_laptop() From ea48eec4949280196eb280cb4b95b0255d09e23b Mon Sep 17 00:00:00 2001 From: Ammad Date: Sun, 28 Jun 2026 23:53:22 +0100 Subject: [PATCH 9/9] Completed Inheritance exercise. --- prep exercises/Inheritance.py | 37 +++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 prep exercises/Inheritance.py diff --git a/prep exercises/Inheritance.py b/prep exercises/Inheritance.py new file mode 100644 index 000000000..87740749f --- /dev/null +++ b/prep exercises/Inheritance.py @@ -0,0 +1,37 @@ +class Parent: + def __init__(self, first_name: str, last_name: str): + self.first_name = first_name + self.last_name = last_name + + def get_name(self) -> str: + return f"{self.first_name} {self.last_name}" + + +class Child(Parent): + def __init__(self, first_name: str, last_name: str): + super().__init__(first_name, last_name) + self.previous_last_names = [] + + def change_last_name(self, last_name) -> None: + self.previous_last_names.append(self.last_name) + self.last_name = last_name + + def get_full_name(self) -> str: + suffix = "" + if len(self.previous_last_names) > 0: + suffix = f" (née {self.previous_last_names[0]})" + return f"{self.first_name} {self.last_name}{suffix}" + +person1 = Child("Elizaveta", "Alekseeva") +print(person1.get_name()) # Should print Elizaveta Actual: Elizaveta Alekseeva +print(person1.get_full_name()) # Should print Elizaveta Alekseeva +person1.change_last_name("Tyurina") +print(person1.get_name()) # Should print Elizaveta Actual: Elizaveta Tyurina +print(person1.get_full_name()) # Should print Elizaveta Alekseeva (née Tyurina) Actual: Elizaveta Tyurina (née Alekseeva) + +person2 = Parent("Elizaveta", "Alekseeva") +print(person2.get_name()) # Should print Elizaveta Alekseeva +# print(person2.get_full_name()) # Method doesn't exist because only Child class has it. +# person2.change_last_name("Tyurina") # Method doesn't exist because only Child class has it. +print(person2.get_name()) # Should print Elizaveta Alekseeva +# print(person2.get_full_name()) # Method doesn't exist because only Child class has it.