diff --git a/sprint5-prep-exercises/LimiOfTypeCheck.py b/sprint5-prep-exercises/LimiOfTypeCheck.py new file mode 100644 index 000000000..9f7a5e528 --- /dev/null +++ b/sprint5-prep-exercises/LimiOfTypeCheck.py @@ -0,0 +1,7 @@ +def double(number): + # return number * 3 + return number * 2 + +print(double(10)) +# The bug is that the double function multiplies the number by 3 instead of 2. +# Since the function is called double, it should multiply the number by 2. \ No newline at end of file diff --git a/sprint5-prep-exercises/PredictInheritance.py b/sprint5-prep-exercises/PredictInheritance.py new file mode 100644 index 000000000..beb48aaad --- /dev/null +++ b/sprint5-prep-exercises/PredictInheritance.py @@ -0,0 +1,58 @@ +# Parent class stores a person's first name and last name +class Parent: + def __init__(self, first_name: str, last_name: str): + self.first_name = first_name + self.last_name = last_name + # Returns the person's first and last name together + def get_name(self) -> str: + return f"{self.first_name} {self.last_name}" + +# Child inherits the fields and methods from Parent +# It also allows the person to change their last name +class Child(Parent): + def __init__(self, first_name: str, last_name: str): + super().__init__(first_name, last_name) + self.previous_last_names = [] + # Changes the last name and saves the old name + def change_last_name(self, last_name) -> None: + self.previous_last_names.append(self.last_name) + self.last_name = last_name + # Returns the current name and the original 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}" +# Create child object +person1 = Child("Elizaveta", "Alekseeva") +# Child inherits get_name() from Parent +# Output: Elizaveta Alekseeva +print(person1.get_name()) +# Child has its own get_full_name() method +# Output: Elizaveta Alekseeva +print(person1.get_full_name()) +# Change the last name +person1.change_last_name("Tyurina") +# The current last name is now Tyurina +# Output: Elizaveta Tyurina +print(person1.get_name()) +# Shows the new name and the previous last name +# Output: Elizaveta Tyurina (née Alekseeva) +print(person1.get_full_name()) +# Create a Parent object +person2 = Parent("Elizaveta", "Alekseeva") +# Parent has get_name(), so this works +# Output: Elizaveta Alekseeva +print(person2.get_name()) +# This causes an AttributeError because Parent +# does not have a get_full_name() method +print(person2.get_full_name()) +# This also causes an AttributeError because Parent +# does not have a change_last_name() method +person2.change_last_name("Tyurina") +# get_name() still works because it belongs to Parent +# Output: Elizaveta Alekseeva +print(person2.get_name()) +# This would cause another AttributeError +# because get_full_name() only exists in Child +print(person2.get_full_name()) \ No newline at end of file diff --git a/sprint5-prep-exercises/accessNotExiProp.py b/sprint5-prep-exercises/accessNotExiProp.py new file mode 100644 index 000000000..5be277df5 --- /dev/null +++ b/sprint5-prep-exercises/accessNotExiProp.py @@ -0,0 +1,11 @@ +class Person: + def __init__(self, name: str): + self.name = name + +def get_name(person: Person) -> str: + return person.name + +def get_age(person: Person) -> int: + return person.age +# As I expected, mypy prints an error saying that Person has no attribute name, +# because the age property does not exist in the Person class. \ No newline at end of file diff --git a/sprint5-prep-exercises/classesAndObjects.py b/sprint5-prep-exercises/classesAndObjects.py new file mode 100644 index 000000000..2cfe36f78 --- /dev/null +++ b/sprint5-prep-exercises/classesAndObjects.py @@ -0,0 +1,17 @@ +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) + +eliza = Person("Eliza", 34, "Arch Linux") +print(eliza.name) +print(eliza.address) + + +# The error means that address doesn't exist in the Person class. We can only +# access attributes that were defined in the class, such as name, age, and preferred_operating_system. \ No newline at end of file diff --git a/sprint5-prep-exercises/enums.py b/sprint5-prep-exercises/enums.py new file mode 100644 index 000000000..6d3c532fa --- /dev/null +++ b/sprint5-prep-exercises/enums.py @@ -0,0 +1,97 @@ +from dataclasses import dataclass +from enum import Enum +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 + + +laptops = [ + Laptop(1, "Dell", "XPS", 13, OperatingSystem.ARCH), + Laptop(2, "Dell", "XPS", 15, OperatingSystem.UBUNTU), + Laptop(3, "Dell", "XPS", 15, OperatingSystem.UBUNTU), + Laptop(4, "Apple", "MacBook", 13, OperatingSystem.MACOS), +] + + +name = input("What is your name? ") + +try: + age = int(input("What is your age? ")) +except ValueError: + print("Age must be a number.", file=sys.stderr) + sys.exit(1) + + +print("Available operating systems:") +for os in OperatingSystem: + print(os.value) + +try: + os_input = input("What is your preferred operating system? ").strip().lower() + + operating_system = next( + os for os in OperatingSystem + if os.value.lower() == os_input + ) + +except StopIteration: + print("Invalid operating system.", file=sys.stderr) + sys.exit(1) + + +person = Person( + name=name, + age=age, + preferred_operating_system=operating_system, +) + + +number_available = sum( + laptop.operating_system == person.preferred_operating_system + for laptop in laptops +) + +print( + f"We have {number_available} laptop(s) with " + f"{person.preferred_operating_system.value}." +) + + +counts: dict[OperatingSystem, int] = {} + +for os in OperatingSystem: + counts[os] = sum( + laptop.operating_system == os + for laptop in laptops + ) + + +most_available_os = max(counts, key=counts.get) + +if most_available_os != person.preferred_operating_system: + if counts[most_available_os] > number_available: + print( + f"If you are willing to use {most_available_os.value}, " + f"you are more likely to get a laptop because we have " + f"{counts[most_available_os]} available." + ) \ No newline at end of file diff --git a/sprint5-prep-exercises/generics.py b/sprint5-prep-exercises/generics.py new file mode 100644 index 000000000..0f857f27a --- /dev/null +++ b/sprint5-prep-exercises/generics.py @@ -0,0 +1,20 @@ +from dataclasses import dataclass +from typing import List + +@dataclass(frozen=True) +class Person: + name: str + children: List["Person"] + age: int + +fatma = Person(name="Fatma", children=[], age=10) +aisha = Person(name="Aisha", children=[], age=13) + +imran = Person(name="Imran", children=[fatma, aisha], age=50) + +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) \ No newline at end of file diff --git a/sprint5-prep-exercises/methods.py b/sprint5-prep-exercises/methods.py new file mode 100644 index 000000000..f449f9c88 --- /dev/null +++ b/sprint5-prep-exercises/methods.py @@ -0,0 +1,23 @@ +import datetime as dt +from dataclasses import dataclass + + +@dataclass +class Person: + name: str + birthdate: dt.date + preferred_operating_system: str + + def is_adult(self) -> bool: + today = dt.date.today() + age = today.year - self.birthdate.year + + if (today.month, today.day) < (self.birthdate.month, self.birthdate.day): + age -= 1 + + return age >= 18 + + +imran = Person("Imran", dt.date(2000, 8, 6), "Ubuntu") + +print(imran.is_adult()) \ No newline at end of file diff --git a/sprint5-prep-exercises/methodsAndFunctions.txt b/sprint5-prep-exercises/methodsAndFunctions.txt new file mode 100644 index 000000000..4f7036323 --- /dev/null +++ b/sprint5-prep-exercises/methodsAndFunctions.txt @@ -0,0 +1,17 @@ +Advantages of Using Methods Instead of Free Functions +# Easier to maintain + Methods keep related operations inside the class, making the program more organized and easier to update. +# Better data protection + A method can control how the object's data is changed and prevent invalid changes. +# Simple to use + Methods can be called using dot notation, such as account.deposit(100), which makes the code easy to read. +# Hides unnecessary details + Users only need to know what a method does and how to call it. They don't need to understand all the code inside it. +# Keeps data and behaviour together + A class can contain both the information about an object and the methods that work with that information. +# Can be reused with different objects + Once a method is defined in a class, objects created from that class can use the same method. +# Allows rules to be enforced + Methods can make sure that data is changed in a safe and valid way. For example, a withdraw() method could prevent a bank account from having a negative balance. +In short: methods are useful because they make code more organized, +reusable, easier to understand, and safer to manage, especially when working with objects and their data. diff --git a/sprint5-prep-exercises/predictDouble.py b/sprint5-prep-exercises/predictDouble.py new file mode 100644 index 000000000..866197dd1 --- /dev/null +++ b/sprint5-prep-exercises/predictDouble.py @@ -0,0 +1,13 @@ +def half(value): + return value / 2 + +def double(value): + return value * 2 + +def second(value): + return value[1] + + +print(double("22")) +# I predicted that it would return an error, but it actually returned 2222. +# I didn't expect this because I thought double() was supposed to work only with numbers. \ No newline at end of file diff --git a/sprint5-prep-exercises/prefOperatingSys.py b/sprint5-prep-exercises/prefOperatingSys.py new file mode 100644 index 000000000..c957aae9c --- /dev/null +++ b/sprint5-prep-exercises/prefOperatingSys.py @@ -0,0 +1,46 @@ +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}") + + # Yes, at first, when I changed str to list[str], + # mypy showed errors as I expected because I was still passing strings instead of lists + # Then I changed the field name to the plural form `preferred_operating_systems` to match the fact that it is now a list. diff --git a/sprint5-prep-exercises/typeAnnotations.py b/sprint5-prep-exercises/typeAnnotations.py new file mode 100644 index 000000000..42ddc6920 --- /dev/null +++ b/sprint5-prep-exercises/typeAnnotations.py @@ -0,0 +1,30 @@ +from typing import Dict +def open_account(balances: dict, 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 = { + "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}")