diff --git a/sprint-5-exercises/__pycache__/enum.cpython-313.pyc b/sprint-5-exercises/__pycache__/enum.cpython-313.pyc new file mode 100644 index 000000000..8575291cb Binary files /dev/null and b/sprint-5-exercises/__pycache__/enum.cpython-313.pyc differ diff --git a/sprint-5-exercises/class-in-python.py b/sprint-5-exercises/class-in-python.py new file mode 100644 index 000000000..5734c2187 --- /dev/null +++ b/sprint-5-exercises/class-in-python.py @@ -0,0 +1,35 @@ +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.age) + +eliza = Person("Eliza", 34, "Arch Linux") +print(eliza.name) +print(eliza.age) + + +def is_adult(person: Person) -> bool: + return person.age >= 18 + + +print(is_adult(imran)) + + +def get_address(person: Person) -> str: + return person.address + # it still gives error as Person class does not have any attribute called address. + + +# this is the error given by mypy: +# class-in-python.py:10: error: "Person" has no attribute "address" [attr-defined] +# class-in-python.py:14: error: "Person" has no attribute "address" [attr-defined] +# Found 2 errors in 1 file (checked 1 source file) + +# Solution: +# The Person class doesn't define an address attribute, but the code tries to access it. To fix the error, either remove the address references or define address as an attribute in the constructor and provide it when creating each Person object. diff --git a/sprint-5-exercises/data-class.py b/sprint-5-exercises/data-class.py new file mode 100644 index 000000000..4b2ffb7ec --- /dev/null +++ b/sprint-5-exercises/data-class.py @@ -0,0 +1,19 @@ +from datetime import date +from dataclasses import dataclass + + +@dataclass +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 + + return age >= 18 + + +imran = Person("Imran", date(2004, 10, 10), "Ubuntu") +print(imran.is_adult()) diff --git a/sprint-5-exercises/enumm.py b/sprint-5-exercises/enumm.py new file mode 100644 index 000000000..c29068496 --- /dev/null +++ b/sprint-5-exercises/enumm.py @@ -0,0 +1,158 @@ +from enum import Enum +from dataclasses import dataclass +import sys + + +# Enum gives us a fixed set of operating system choices. +class OperatingSystem(Enum): + MACOS = "macOS" + ARCH = "Arch Linux" + UBUNTU = "Ubuntu" + + +# Dataclass automatically creates the __init__ method for us. +@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 + + +# The laptops already available in the library. +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, + ), +] + + +# Get the user's name. +name = input("What is your name? ") + + +# Convert the age from a string to an integer. +# If conversion fails, print the error to stderr and exit with code 1. +try: + age = int(input("What is your age? ")) +except ValueError: + print("Invalid age.", file=sys.stderr) + sys.exit(1) + + +# Convert the user's input into an OperatingSystem enum value. +# If the value isn't one of our enum choices, exit with an error. +try: + preferred_operating_system = OperatingSystem( + input("What is your preferred operating system? ") + ) +except ValueError: + print("Invalid operating system.", file=sys.stderr) + sys.exit(1) + + +# Create a Person using the validated input. +person = Person( + name=name, + age=age, + preferred_operating_system=preferred_operating_system, +) + + +# Count laptops matching the person's preferred operating system. +count = 0 + +for laptop in laptops: + if laptop.operating_system == person.preferred_operating_system: + count += 1 + + +print( + f"There are {count} laptops available with " + f"{person.preferred_operating_system.value}." +) + + +# Count how many laptops are available for each operating system. +available_laptops = {} + +for laptop in laptops: + os = laptop.operating_system + + if os not in available_laptops: + available_laptops[os] = 0 + + available_laptops[os] += 1 + + +# Find the operating system with the most available laptops. +most_available_os = max( + available_laptops, + key=available_laptops.__getitem__, +) + + +# If another operating system has more laptops, recommend it. +if most_available_os != person.preferred_operating_system: + print( + f"You are more likely to get a laptop if you accept " + f"{most_available_os.value}." + ) + + +# # LAPTOP LIBRARY PROGRAM FLOW: +# +# 1. Define OperatingSystem enum +# ↓ +# 2. Define Person and Laptop dataclasses +# ↓ +# 3. Create list of available laptops +# ↓ +# 4. Get user's name, age, and preferred OS +# ↓ +# 5. Validate and convert user input +# ↓ +# 6. Create a Person object +# ↓ +# 7. Count laptops matching the user's preferred OS +# ↓ +# 8. Count laptops for each operating system +# ↓ +# 9. Find the OS with the most laptops +# ↓ +# 10. Compare it with the user's preferred OS +# ↓ +# 11. Recommend another OS if more laptops are available diff --git a/sprint-5-exercises/generics.py b/sprint-5-exercises/generics.py new file mode 100644 index 000000000..fff9773f2 --- /dev/null +++ b/sprint-5-exercises/generics.py @@ -0,0 +1,23 @@ +from dataclasses import dataclass + + +@dataclass(frozen=True) +class Person: + name: str + age: int + children: list + + +fatma = Person(name="Fatma", age= 22, children=[]) +aisha = Person(name="Aisha", age = 15, children=[]) + +imran = Person(name="Imran", age = 45, 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/sprint-5-exercises/inheretance.py b/sprint-5-exercises/inheretance.py new file mode 100644 index 000000000..d6fa16b43 --- /dev/null +++ b/sprint-5-exercises/inheretance.py @@ -0,0 +1,72 @@ +from typing import Iterable, Optional + + +class ImmutableNumberList: + # We accept any `Iterable[int]` here, so can construct with a list, a set, or anything else that can be iterated. + def __init__(self, elements: Iterable[int]): + # We copy the elements so that if someone mutates the passed in elements list, our copy won't be mutated. + self.elements = [element for element in elements] + + def first(self) -> Optional[int]: + if not self.elements: + return None + return self.elements[0] + + def last(self) -> Optional[int]: + if not self.elements: + return None + return self.elements[-1] + + def length(self) -> int: + return len(self.elements) + + def largest(self) -> Optional[int]: + # To find the largest element, we need to go through the entire list (which may take some time). + if not self.elements: + return None + largest = self.elements[0] + for element in self.elements: + if element > largest: + largest = element + return largest + + +# A SortedImmutableNumberList is the same as an ImmutableNumberList, +# but it changes some aspects. +class SortedImmutableNumberList(ImmutableNumberList): + def __init__(self, elements: Iterable[int]): + # We do extra work here when constructing the list, + # to make sure the elements are sorted. + # This takes more time than the ImmutableNumberList version would. + super().__init__(sorted(elements)) + + # This method overrides (replaces) the method with the same name on the super-class. + def largest(self) -> Optional[int]: + # Because we know the elements were already sorted in the constructor, + # we can implement finding the largest number faster. + # We don't need to look through every element - we know the largest element is at the end. + # Because we did extra work one time before (in the constructor), + # we can avoid re-doing that work every time someone calls `largest()`. + return self.last() + + def max_gap_between_values(self) -> Optional[int]: + if not self.elements: + return None + previous_element = None + max_gap = -1 + for element in self.elements: + if previous_element is not None: + gap = element - previous_element + if gap > max_gap: + max_gap = gap + previous_element = element + return max_gap + + +values = SortedImmutableNumberList([1, 19, 7, 13, 4]) +print(values.largest()) +print(values.max_gap_between_values()) + +unsorted_values = ImmutableNumberList([1, 19, 7, 13, 4]) +print(unsorted_values.largest()) +# print(unsorted_values.max_gap_between_values()) diff --git a/sprint-5-exercises/method-vs-free-function.txt b/sprint-5-exercises/method-vs-free-function.txt new file mode 100644 index 000000000..c467feb3e --- /dev/null +++ b/sprint-5-exercises/method-vs-free-function.txt @@ -0,0 +1,13 @@ +Advantages of methods: + +They keep related code together. A method belongs to a class, so it keeps the behavior related to that object in one place. + +They are easier to understand. When you see person.is_adult(), it is clear that the action is related to a Person. + +They can directly access the object's data. A method can use self to access attributes of the object. + +They make code more organized. Classes group the data and the operations that work with that data together. + +They can make code easier to reuse. Once a class has a method, every instance of that class can use it. + +Encapsulation - if we change the implementation of Person (e.g. we start storing a date of birth instead of an age), it’s more obvious what things we need to change. \ No newline at end of file diff --git a/sprint-5-exercises/method.py b/sprint-5-exercises/method.py new file mode 100644 index 000000000..d60609496 --- /dev/null +++ b/sprint-5-exercises/method.py @@ -0,0 +1,18 @@ +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 + + return age >= 18 + + +imran = Person("Imran", date(2004, 10, 10), "Ubuntu") +print(imran.is_adult()) diff --git a/sprint-5-exercises/play-computer.py b/sprint-5-exercises/play-computer.py new file mode 100644 index 000000000..d7ae479bd --- /dev/null +++ b/sprint-5-exercises/play-computer.py @@ -0,0 +1,54 @@ +class Parent: + + # this is constructor and sets attributes + def __init__(self, first_name: str, last_name: str): + self.first_name = first_name + self.last_name = last_name + + # this is a method that gets name and last name + def get_name(self) -> str: + return f"{self.first_name} {self.last_name}" + + +# Child inherits attributes and methods from Parent +class Child(Parent): + # child class constructor + def __init__(self, first_name: str, last_name: str): + + # Call the Parent constructor to initialise first_name and last_name + super().__init__(first_name, last_name) + self.previous_last_names: list[str] = [] + + # Save the current last name, then change it to the new last name + def change_last_name(self, last_name) -> None: + self.previous_last_names.append(self.last_name) + self.last_name = last_name + + # this method gets the fullname + 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}" + + +# creating object of Child class +person1 = Child("Elizaveta", "Alekseeva") +# prints the name of person1 which will be (Elizaveta Alekseeva) +print(person1.get_name()) +# # Prints the new full name and the previous last name +print(person1.get_full_name()) +# prints the new last name by calling the change_last_name method with a new parameter +person1.change_last_name("Tyurina") +# here again it prints the name of person1 +print(person1.get_name()) +# prints the fullname of person1, but this time the last name ll be other than before +print(person1.get_full_name()) + +# same cycle with the object of parent class this time +person2 = Parent("Elizaveta", "Alekseeva") +print(person2.get_name()) +#print(person2.get_full_name()) +#person2.change_last_name("Tyurina") +print(person2.get_name()) +#print(person2.get_full_name()) diff --git a/sprint-5-exercises/type-checking-mypy.py b/sprint-5-exercises/type-checking-mypy.py new file mode 100644 index 000000000..fba8cbc5f --- /dev/null +++ b/sprint-5-exercises/type-checking-mypy.py @@ -0,0 +1,41 @@ +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 += 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", 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}") + +# After running mypy tool then I found 3 errors as below: + +# type-checking-mypy.py:27: error: Missing positional argument "amount" in call to "open_account" [call-arg] +# type-checking-mypy.py:28: error: Missing positional argument "amount" in call to "open_account" [call-arg] +# type-checking-mypy.py:31: error: Name "format_pence_as_str" is not defined [name-defined] + +# to fix the bug we need to add the positional argument in open account function and call the correct name of the second function. "format_pence_as_string" diff --git a/sprint-5-exercises/type-exercise-1.py b/sprint-5-exercises/type-exercise-1.py new file mode 100644 index 000000000..f1b93dba3 --- /dev/null +++ b/sprint-5-exercises/type-exercise-1.py @@ -0,0 +1,27 @@ +def half(value: int) -> float: + return value / 2 + + +def double(value: int) -> int: + return value * 2 + + +def second(value: int) -> int: + return value[1] + + +print(half("22")) +print(double("22")) +print(second("22")) + +# python throw the error after running the program, like "unsupported operand type"; + + +# while type annotation give mypy tool to check the code before running the program and detects certain bugs; +""" +type-exercise-1.py:10: error: Value of type "int" is not indexable [index] +type-exercise-1.py:13: error: Argument 1 to "half" has incompatible type "str"; expected "int" [arg-type] +type-exercise-1.py:14: error: Argument 1 to "double" has incompatible type "str"; expected "int" [arg-type] +type-exercise-1.py:15: error: Argument 1 to "second" has incompatible type "str"; expected "int" [arg-type] + +""" diff --git a/sprint-5-exercises/type-guided-refactoring.py b/sprint-5-exercises/type-guided-refactoring.py new file mode 100644 index 000000000..d8af20491 --- /dev/null +++ b/sprint-5-exercises/type-guided-refactoring.py @@ -0,0 +1,67 @@ +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 == 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/sprint-5-exercises/type-limit-exercise-2.py b/sprint-5-exercises/type-limit-exercise-2.py new file mode 100644 index 000000000..08f74c28d --- /dev/null +++ b/sprint-5-exercises/type-limit-exercise-2.py @@ -0,0 +1,24 @@ +def double(number): + return number * 3 + + +print(double(10)) + +# our function is supposed to double the input value, but the function return triple. +# to fix this we have two options: + + +# First: we can change the name of function from double to triple. +def triple(number): + return number * 3 + + +print(triple(10)) + + +# Second: we should multiply the input by 2. +def double(number): + return number * 2 + + +print(double(10))