diff --git a/prep_exercise/1a_why_we_use_types.py b/prep_exercise/1a_why_we_use_types.py new file mode 100644 index 00000000..eb73f692 --- /dev/null +++ b/prep_exercise/1a_why_we_use_types.py @@ -0,0 +1,22 @@ +# ========== exercise 1 ================================== +# Predict what double("22") will do. Then run the code and check. Did it do what you expected? Why did it return the value it did? + +def half(value): + return value / 2 + +def double(value): + return value * 2 + +def second(value): + return value[1] + +print(double("22")) + +# My prediction was that double("22") would print 44. +# After running the code, I realized it prints "2222" because +# multiplying a string by an integer in Python repeats the string. +# +# What I have learned is that in Python, the * operator behaves +# differently depending on the data type. For example: +# - with integers, it performs mathematical multiplication +# - with strings, it performs string repetition diff --git a/prep_exercise/1b_why_we_use_types.py b/prep_exercise/1b_why_we_use_types.py new file mode 100644 index 00000000..f4a6379f --- /dev/null +++ b/prep_exercise/1b_why_we_use_types.py @@ -0,0 +1,15 @@ +# ================= exercise =============================== +# Read the code and write down what the bug is. How would you fix it? + +def double(number): + return number * 3 + +print(double(10)) + +# The bug in the code is that the function is named double(), but it multiplies +# the number by 3 instead of 2. This makes the function behavior inconsistent +# with its name. + +# To fix this, either: +# - we need to change the function to return number * 2, or +# - rename the function to triple() if multiplying by 3 is the intended behavior. diff --git a/prep_exercise/2_type_checking_with_mypy.py b/prep_exercise/2_type_checking_with_mypy.py new file mode 100644 index 00000000..4b31de68 --- /dev/null +++ b/prep_exercise/2_type_checking_with_mypy.py @@ -0,0 +1,31 @@ + +def open_account(balances, name, amount): + balances[name] = amount + +def sum_balances(accounts): + total = 0 + for name, pence in accounts.items(): + print(f"{name} had balance {pence}") + total += int(pence) + return total + +def format_pence_as_string(total_pence): + if total_pence < 100: + return f"{total_pence}p" + pounds = int(total_pence / 100) + pence = total_pence % 100 + return f"£{pounds}.{pence:02d}" + +balances = { + "Sima": 700, + "Linn": 545, + "Georg": 831, +} + +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_exercise/3_classes_and_objects.py b/prep_exercise/3_classes_and_objects.py new file mode 100644 index 00000000..0d1aca15 --- /dev/null +++ b/prep_exercise/3_classes_and_objects.py @@ -0,0 +1,26 @@ +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 + +imran = Person("Imran", 22, "Ubuntu", "London street") +print(imran.name) +print(imran.address) + +eliza = Person("Eliza", 34, "Arch Linux", "Hatfield town") +print(eliza.name) +print(eliza.address) + +# ====function to check if imran is an adult=========== +def is_adult(person: Person) -> bool: + return person.age >= 18 + +print(is_adult(imran)) + + +def address(person: Person) -> str: + return person.city #city property does not exist + +print(address(imran)) diff --git a/prep_exercise/4_methods.py b/prep_exercise/4_methods.py new file mode 100644 index 00000000..9d2a483c --- /dev/null +++ b/prep_exercise/4_methods.py @@ -0,0 +1,24 @@ +# exercise +# Change the Person class to take a date of birth (using the standard library’s datetime.date class) and store it in a field instead of age. +# Update the is_adult method to act the same as before. + + +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 = age - 1 + + return age >= 18 + +imran = Person("Imran", date(2005, 5, 12), "Ubuntu") +print(imran.is_adult()) diff --git a/prep_exercise/5_Dataclasses.py b/prep_exercise/5_Dataclasses.py new file mode 100644 index 00000000..9a8b8eff --- /dev/null +++ b/prep_exercise/5_Dataclasses.py @@ -0,0 +1,28 @@ +# ✍️exercise +# Write a Person class using @datatype which uses a datetime.date for date of birth, rather than an int for age. +# Re-add the is_adult method to it. + +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 = age - 1 + + return age >= 18 + +imran = Person("Imran", date(2000,5,12), "Ubuntu") +print(imran) + +imran2 = Person("Imran",date(2000,5,12), "Ubuntu") +print(imran == imran2) # Prints True diff --git a/prep_exercise/6_generics.py b/prep_exercise/6_generics.py new file mode 100644 index 00000000..56c59d5e --- /dev/null +++ b/prep_exercise/6_generics.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= 22, children=[]) +aisha = Person(name="Aisha", age =21, children=[]) + +imran = Person(name="Imran", age = 33, 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) diff --git a/prep_exercise/7_type_guided_refactoring.py b/prep_exercise/7_type_guided_refactoring.py new file mode 100644 index 00000000..315e89be --- /dev/null +++ b/prep_exercise/7_type_guided_refactoring.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: List[Laptop] = [] + 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}") diff --git a/prep_exercise/8_enums.py b/prep_exercise/8_enums.py new file mode 100644 index 00000000..ecccc20f --- /dev/null +++ b/prep_exercise/8_enums.py @@ -0,0 +1,104 @@ +from dataclasses import dataclass +from enum import Enum +from typing import List, Dict +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 + +# =====define parse age====== +def parse_age(raw: str) -> int: + try: + age= int(raw) + except ValueError: + print("Error: age must be asn integer.", file=sys.stderr) + sys.exit(1) + + if age <= 0: + print("Error: age must be a positive integer.", file=sys.stderr) + sys.exit(1) + + return age + +# =====define parse operating system====== +def parse_operating_system(raw: str) -> OperatingSystem: + normalized = raw.strip().lower() + + aliases = { + "macos": OperatingSystem.MACOS, + "mac": OperatingSystem.MACOS, + "arch": OperatingSystem.ARCH, + "arch linux": OperatingSystem.ARCH, + "ubuntu": OperatingSystem.UBUNTU, + } + + if normalized not in aliases: + valid = ", ".join(sorted(aliases.keys())) + print(f"Error: invalid operating system. Try one of: {valid}", file=sys.stderr) + sys.exit(1) + + return aliases[normalized] + + +# =======count laptops by os============ +def count_laptops_by_os(laptops: List[Laptop]) -> Dict[OperatingSystem, int]: + counts: Dict[OperatingSystem, int] = {os: 0 for os in OperatingSystem} + for laptop in laptops: + counts[laptop.operating_system] +=1 + return counts + + +# ======define main ================ +def main() -> None: + laptops = [ + Laptop(id=1, manufacturer="Dell", model="XPS", screen_size_in_inches=13.0, operating_system=OperatingSystem.ARCH), + Laptop(id=2, manufacturer="Dell", model="XPS", screen_size_in_inches=15.0, operating_system=OperatingSystem.UBUNTU), + Laptop(id=3, manufacturer="Dell", model="XPS", screen_size_in_inches=15.0, operating_system=OperatingSystem.UBUNTU), + Laptop(id=4, manufacturer="Apple", model="macBook", screen_size_in_inches=13.0, operating_system=OperatingSystem.MACOS), +] + + name = input("Enter your name: ").strip() + if not name: + print("Error: name cannot be empty.", file=sys.stderr) + sys.exit(1) + + age = parse_age(input("Enter your age: ")) + preferred_os = parse_operating_system(input("preferred OS(Ubuntu / Arch / macOS): ")) + + person = Person(name=name, age=age, preferred_operating_system=preferred_os) + + counts = count_laptops_by_os(laptops) + preferred_count = counts[person.preferred_operating_system] + + print(f"\nHi {person.name} (age {person.age})") + print(f"Laptops available with {person.preferred_operating_system.value}: {preferred_count}") + + # Find the OS with maximum availability + best_os = max(counts, key=lambda os:counts[os]) + best_count = counts[best_os] + + if best_os != person.preferred_operating_system and best_count > preferred_count: + print( + f"If you are willing to accept {best_os.value} instead," + f"You're more likely to get a laptop. {best_count} available)." + ) + +if __name__ == "__main__": + main() diff --git a/prep_exercise/9_inheritance.py b/prep_exercise/9_inheritance.py new file mode 100644 index 00000000..5804118f --- /dev/null +++ b/prep_exercise/9_inheritance.py @@ -0,0 +1,52 @@ +# play computer game with the code below and predict what you expect each line will do. Then run the code and check your prediction (If any lines cause errors, you may need to comment them out to check later lines). + +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) -> 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()) #works because get_name is inherited from Parent. +print(person1.get_full_name()) # get_full_name() works, exist in child. +person1.change_last_name("Tyurina") #change_last_name("Tyurina") works, exist in child.. +print(person1.get_name()) # works because it comes from Parent.get_name. +print(person1.get_full_name())# works because it comes from Parent.get_name. + + +person2 = Parent("Elizaveta", "Alekseeva") +print(person2.get_name()) # works because it is in the Parent class. +print(person1.get_full_name()) +# print(person2.get_full_name()) # crashes the program as get_full_name is defined only in child +# person2.change_last_name("Tyurina") # crashes the program as change_last_name is defined only in child not in parent +print(person2.get_name()) +# print(person2.get_full_name()) #get_full_name is defined only in child not in parent + + +# ===============What I learned from playing computer game with above code. + +# 1. Objects can only call methods they actually have inside them. +# - Creating a parent object does not automatically give the child its behavior + +# ===================================================================== +#2. Inheritance is "is-a", not "might-be" i.e +# A child is a parent(here the child can inherit freely from the father.) +# A parent is NOT a child(i.e a parent does not inherit from the child)