diff --git a/prep-exercises/.gitignore b/prep-exercises/.gitignore new file mode 100644 index 0000000..726801d --- /dev/null +++ b/prep-exercises/.gitignore @@ -0,0 +1,23 @@ +# Virtual Environment +venv/ +env/ +ENV/ + +# Python cache +__pycache__/ +*.pyc +*.pyo +*.pyd + +# mypy cache +.mypy_cache/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store + diff --git a/prep-exercises/classes_and_objects.py b/prep-exercises/classes_and_objects.py new file mode 100644 index 0000000..ba6dadc --- /dev/null +++ b/prep-exercises/classes_and_objects.py @@ -0,0 +1,25 @@ +class Person: + def __init__(self, name: str, age: int, preferred_operating_system: str, address: str): + self.name = name + self.age = age + self.preferred_operating_system = preferred_operating_system + self.address = address + + def is_adult(self) -> bool: + return self.age >= 18 + + +imran = Person("Imran", 22, "Ubuntu", "123 Main St") +print(imran.name) +print(imran.address) +print(imran.is_adult()) + +eliza = Person("Eliza", 34, "Arch Linux", "456 Oak Ave") +print(eliza.name) +print(eliza.address) + + +def get_address(person: Person) -> str: + return person.address + +print(get_address(imran)) diff --git a/prep-exercises/dataclasses_exercise.py b/prep-exercises/dataclasses_exercise.py new file mode 100644 index 0000000..d6cdabe --- /dev/null +++ b/prep-exercises/dataclasses_exercise.py @@ -0,0 +1,25 @@ +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) -> bool: + current_date = date.today() + age = current_date.year - self.date_of_birth.year - ( + (current_date.month, current_date.day) < (self.date_of_birth.month, self.date_of_birth.day) + ) + return age >= 18 + +# Example usage: +imran = Person("Imran", date(2001, 5, 15), "Ubuntu") +print(imran) +print(imran.is_adult()) + +eliza = Person("Eliza", date(1990, 3, 20), "Arch Linux") +print(eliza) +print(eliza.is_adult()) + diff --git a/prep-exercises/enums_exercise.py b/prep-exercises/enums_exercise.py new file mode 100644 index 0000000..0da90ec --- /dev/null +++ b/prep-exercises/enums_exercise.py @@ -0,0 +1,70 @@ +from dataclasses import dataclass +from enum import Enum + +class OperatingSystem(Enum): + MACOS = "macOS" + UBUNTU = "Ubuntu" + ARCH = "Arch Linux" + +@dataclass +class Laptop: + id: int + manufacturer: str + model: str + screen_size_in_inches: float + operating_system: OperatingSystem + +laptops = [ + Laptop(id=1, manufacturer="Lenovo", model="ThinkPad", screen_size_in_inches=14, operating_system=OperatingSystem.ARCH), + Laptop(id=2, manufacturer="HP", model="Pavilion", screen_size_in_inches=15.6, operating_system=OperatingSystem.UBUNTU), + Laptop(id=3, manufacturer="Asus", model="ZenBook", screen_size_in_inches=13.3, operating_system=OperatingSystem.UBUNTU), + Laptop(id=4, manufacturer="Apple", model="MacBook Air", screen_size_in_inches=13, operating_system=OperatingSystem.MACOS), +] + +@dataclass +class Person: + name: str + age: int + preferred_operating_system: OperatingSystem + + +name = input('Please enter your name: ') + +age_input = input('Please enter your age: ') +try: + age = int(age_input) +except ValueError: + print(f"Error: '{age_input}' is not a valid age. Please enter a number.") + exit() + +preferred_os_input = input('Please enter your preferred operating system: ') +try: + preferred_operating_system = OperatingSystem(preferred_os_input) +except ValueError: + available_options = ', '.join([os.value for os in OperatingSystem]) + print(f"Error: '{preferred_os_input}' is not available. Please choose from: {available_options}") + exit() + + +person = Person(name=name, age=age, preferred_operating_system=preferred_operating_system) + + +number_of_available_laptops = sum( + 1 for laptop in laptops if laptop.operating_system == person.preferred_operating_system +) + +def offer_laptop_to_user() -> None: + offer = input('Would you like a laptop with this OS (yes / no) ? ') + if offer.lower() == "y" or offer.lower() == "yes": + print('Great! Please come on monday next week to collect your laptop') + else: + print('No problem. See you later.') + +if number_of_available_laptops == 1: + print(f'There is {number_of_available_laptops} laptop available with your preferred operating system.') + offer_laptop_to_user() +elif number_of_available_laptops > 1: + print(f'There are {number_of_available_laptops} laptops available with your preferred operating system.') + offer_laptop_to_user() +else: + print('There are no laptops available with your preferred operating system.') diff --git a/prep-exercises/generics.py b/prep-exercises/generics.py new file mode 100644 index 0000000..84aed4a --- /dev/null +++ b/prep-exercises/generics.py @@ -0,0 +1,48 @@ +from dataclasses import dataclass +from typing import List +from datetime import date + +@dataclass(frozen=True) +class Person: + name: str + date_of_birth: date + children: List["Person"] + + def age(self) -> int: + current_date = date.today() + age = current_date.year - self.date_of_birth.year - ( + (current_date.month, current_date.day) < (self.date_of_birth.month, self.date_of_birth.day) + ) + return age + +# Calculate dates of birth based on current age +def years_ago(years: int) -> date: + today = date.today() + try: + return date(today.year - years, today.month, today.day) + except ValueError: # Handle leap year edge case (Feb 29) + return date(today.year - years, today.month, today.day - 1) + +sara = Person(name="Sara", date_of_birth=years_ago(5), children=[]) +ahmed = Person(name="Ahmed", date_of_birth=years_ago(8), children=[]) + +ali = Person(name="Ali", date_of_birth=years_ago(28), children=[sara]) +aya = Person(name="Aya", date_of_birth=years_ago(32), children=[ahmed]) + +imran = Person(name="Imran", date_of_birth=years_ago(55), children=[ali, aya]) + +def print_family_tree(person: Person) -> None: + print(f"{person.name} ({person.age()})") + for child in person.children: + print(f" - {child.name} ({child.age()})") + for grandchild in child.children: + print(f" - {grandchild.name} ({grandchild.age()})") + +def count_family_members(person: Person) -> int: + count = 1 + for child in person.children: + count += count_family_members(child) + return count + +print_family_tree(imran) +print(f"\nTotal family members: {count_family_members(imran)}") diff --git a/prep-exercises/inheritance.py b/prep-exercises/inheritance.py new file mode 100644 index 0000000..c5b0acf --- /dev/null +++ b/prep-exercises/inheritance.py @@ -0,0 +1,40 @@ +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: list[str] = [] + + def change_last_name(self, last_name: str) -> 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}" + + +print("Creating Child instance:") +person1 = Child("Sarah", "Johnson") +print(f"Name: {person1.get_name()}") +print(f"Full name: {person1.get_full_name()}") + +print("\nChanging last name to Smith:") +person1.change_last_name("Smith") +print(f"Name: {person1.get_name()}") +print(f"Full name: {person1.get_full_name()}") + +print("\nCreating Parent instance:") +person2 = Parent("Emma", "Wilson") +print(f"Name: {person2.get_name()}") + +# person2.change_last_name("Brown") # Error: Parent doesn't have change_last_name method diff --git a/prep-exercises/methods.py b/prep-exercises/methods.py new file mode 100644 index 0000000..52a0921 --- /dev/null +++ b/prep-exercises/methods.py @@ -0,0 +1,22 @@ +from datetime import date + +class Person: + def __init__(self, name: str, DoB: date, preferred_operating_system: str): + self.name = name + self.DoB = DoB + self.preferred_operating_system = preferred_operating_system + + def is_adult(self) -> bool: + current_date = date.today() + age = current_date.year - self.DoB.year - ( + (current_date.month, current_date.day) < (self.DoB.month, self.DoB.day) + ) + return age >= 18 + + + +eliza = Person("Eliza", date(2010, 5, 15), "Arch Linux") +print(f"{eliza.name} is adult: {eliza.is_adult()}") + +sara = Person("Sara", date(1995, 12, 20), "macOS") +print(f"{sara.name} is adult: {sara.is_adult()}") diff --git a/prep-exercises/requirements.txt b/prep-exercises/requirements.txt new file mode 100644 index 0000000..76695b3 --- /dev/null +++ b/prep-exercises/requirements.txt @@ -0,0 +1 @@ +mypy==1.18.2 diff --git a/prep-exercises/type_checking_with_mypy.py b/prep-exercises/type_checking_with_mypy.py new file mode 100644 index 0000000..149aa06 --- /dev/null +++ b/prep-exercises/type_checking_with_mypy.py @@ -0,0 +1,34 @@ +def open_account(balances: dict[str, int], name: str, amount: int) -> None: + balances[name] = amount + +def sum_balances(accounts: dict[str, int]) -> int: + total = 0 + for name, pence in accounts.items(): + print(f"{name} had balance {pence}") + total += pence + return total + +def format_pence_as_string(total_pence: int) -> str: + if total_pence < 100: + return f"{total_pence}p" + pounds = int(total_pence / 100) + pence = total_pence % 100 + return f"£{pounds}.{pence:02d}" + +balances: dict[str, int] = { + "Sima": 700, + "Linn": 545, + "Georg": 831, +} + +# Bugs fixed: +# 1. Added balances as first argument +# 2. Converted amounts to pence (int) +# 3. Fixed function name typo +open_account(balances, "Tobi", 913) +open_account(balances, "Olya", 713) + +total_pence = sum_balances(balances) +total_string = format_pence_as_string(total_pence) + +print(f"The bank accounts total {total_string}") diff --git a/prep-exercises/type_guided_refactorings.py b/prep-exercises/type_guided_refactorings.py new file mode 100644 index 0000000..e1b7561 --- /dev/null +++ b/prep-exercises/type_guided_refactorings.py @@ -0,0 +1,55 @@ +from dataclasses import dataclass +from typing import Optional + +# Before refactoring - unclear types +def process_user_data(data): + name = data[0] + age = data[1] + email = data[2] if len(data) > 2 else None + + if age < 18: + return None + + return f"{name} ({age}): {email or 'no email'}" + + +# After refactoring - clear types +@dataclass +class User: + name: str + age: int + email: Optional[str] = None + +def is_adult(user: User) -> bool: + return user.age >= 18 + +def format_user_info(user: User) -> str: + email_str = user.email if user.email else "no email" + return f"{user.name} ({user.age}): {email_str}" + +def process_user(user: User) -> Optional[str]: + if not is_adult(user): + return None + return format_user_info(user) + + +# Testing old version +print(process_user_data(("Alice", 25, "alice@example.com"))) +print(process_user_data(("Bob", 16))) + +# Testing new version +user1 = User("Alice", 25, "alice@example.com") +user2 = User("Bob", 16) + +print(process_user(user1)) +print(process_user(user2)) + + +# Another example +def get_value(key: str, data: dict[str, int]) -> Optional[int]: + return data.get(key) + +sample_data = {"a": 1, "b": 2} +result = get_value("a", sample_data) +if result is not None: + print(f"Found: {result}") diff --git a/prep-exercises/why_we_use_types.py b/prep-exercises/why_we_use_types.py new file mode 100644 index 0000000..df4270d --- /dev/null +++ b/prep-exercises/why_we_use_types.py @@ -0,0 +1,28 @@ +def half(value): + return value / 2 + +def double(value): + return value * 2 + +def second(value): + return value[1] + +# Prediction: double("22") will return "2222" because * operator repeats strings +print(double("22")) + +# Testing other cases +print(half(22)) +# print(half("hello")) # This will error +# print(half("22")) # This will error + +print(double(22)) +print(double("hello")) + +# print(second(22)) # This will error +print(second("hello")) +print(second("22")) + +def double_bug(number): + return number * 2 + +print(double_bug(10)) # Returns 20 (correctly doubles the number) \ No newline at end of file