diff --git a/sprint-5-prep-exercises/.gitignore b/sprint-5-prep-exercises/.gitignore new file mode 100644 index 000000000..1d17dae13 --- /dev/null +++ b/sprint-5-prep-exercises/.gitignore @@ -0,0 +1 @@ +.venv diff --git a/sprint-5-prep-exercises/exercise_01.py b/sprint-5-prep-exercises/exercise_01.py new file mode 100644 index 000000000..7b427488d --- /dev/null +++ b/sprint-5-prep-exercises/exercise_01.py @@ -0,0 +1,12 @@ +# Exercise +# Predict what double("22") will do. +# It will treat "22" as a integer and return 11 as an integer + +# Then run the code and check. +def double(value): + return value * 2 + +print(double("22")) + +# Did it do what you expected? Why did it return the value it did? +# No, it treated it like a string, so instead of trying to do math, it did string repetition. \ No newline at end of file diff --git a/sprint-5-prep-exercises/exercise_02.py b/sprint-5-prep-exercises/exercise_02.py new file mode 100644 index 000000000..e49491139 --- /dev/null +++ b/sprint-5-prep-exercises/exercise_02.py @@ -0,0 +1,10 @@ +def double(number): + return number * 3 + +print(double(10)) + +# Read the above code and write down what the bug is. How would you fix it? + +# Potential bug: Code says double and it is multiplying by 3 + +# It could be fixed by changing the name of the function to triple, or the integer to 2. \ No newline at end of file diff --git a/sprint-5-prep-exercises/exercise_03.py b/sprint-5-prep-exercises/exercise_03.py new file mode 100644 index 000000000..76e29a45c --- /dev/null +++ b/sprint-5-prep-exercises/exercise_03.py @@ -0,0 +1,30 @@ +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 = { + "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}") \ No newline at end of file diff --git a/sprint-5-prep-exercises/exercise_04_and_05.py b/sprint-5-prep-exercises/exercise_04_and_05.py new file mode 100644 index 000000000..adcd07280 --- /dev/null +++ b/sprint-5-prep-exercises/exercise_04_and_05.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.address) + +eliza = Person("Eliza", 34, "Arch Linux") +print(eliza.name) +print(eliza.address) + +# exercise_4_and_5.py:9: error: "Person" has no attribute "address" [attr-defined] +# exercise_4_and_5.py:13: error: "Person" has no attribute "address" [attr-defined] +# Found 2 errors in 1 file (checked 1 source file) + +# There is no address attribute for a Person, only name, age and preferred operating system. + +# Add the is_adult code to the file you saved earlier. +def is_adult(person: Person) -> bool: + return person.age >= 18 + +print(is_adult(imran)) + +# Run it through mypy - notice that no errors are reported - +# mypy understands that Person has a property named age so is happy with the function. + +def is_banana(person: Person) -> bool: + return person.banana + +print(is_banana(imran)) + +# exercise_4_and_5.py:31: error: "Person" has no attribute "banana" [attr-defined] \ No newline at end of file diff --git a/sprint-5-prep-exercises/exercise_06.py b/sprint-5-prep-exercises/exercise_06.py new file mode 100644 index 000000000..a1a2b3a8d --- /dev/null +++ b/sprint-5-prep-exercises/exercise_06.py @@ -0,0 +1,4 @@ +# Think of the advantages of using methods instead of free functions. Write them down in your notebook. + +# Better readability of code +# Encapsulation of that function only to class - cleaner and better security. \ No newline at end of file diff --git a/sprint-5-prep-exercises/exercise_07.py b/sprint-5-prep-exercises/exercise_07.py new file mode 100644 index 000000000..72a2bc90b --- /dev/null +++ b/sprint-5-prep-exercises/exercise_07.py @@ -0,0 +1,26 @@ +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): + dob = self.dob + today = date.today() + if dob.year > today.year - 18: return False + if dob.year == today.year - 18: + if dob.month > today.month: return False + if dob.month == today.month: + if dob.day > today.day: return False + return True + + +imran = Person("Imran", date(2004, 8, 31), "Ubuntu") +print(imran.is_adult()) + +# 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. \ No newline at end of file diff --git a/sprint-5-prep-exercises/exercise_08.py b/sprint-5-prep-exercises/exercise_08.py new file mode 100644 index 000000000..8b1ef9530 --- /dev/null +++ b/sprint-5-prep-exercises/exercise_08.py @@ -0,0 +1,24 @@ +# Write a Person class using @datatype which uses a datetime.date for date of birth, rather than an int for age. +from dataclasses import dataclass +from datetime import date + +@dataclass(frozen=True) +class Person: + name: str + dob: date + preferred_operating_system: str + +# Re-add the is_adult method to it. + def is_adult(self): + dob = self.dob + today = date.today() + if dob.year > today.year - 18: return False + if dob.year == today.year - 18: + if dob.month > today.month: return False + if dob.month == today.month: + if dob.day > today.day: return False + return True + + +imran = Person("Imran", date(2004, 8, 31), "Ubuntu") +print(imran.is_adult()) \ No newline at end of file diff --git a/sprint-5-prep-exercises/exercise_09.py b/sprint-5-prep-exercises/exercise_09.py new file mode 100644 index 000000000..c406bf6ff --- /dev/null +++ b/sprint-5-prep-exercises/exercise_09.py @@ -0,0 +1,24 @@ +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=82) +aisha = Person(name="Aisha", children=[], age=83) + +imran = Person(name="Imran", children=[fatma, aisha], age=102) + +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) + +# Fix the above code so that it works. +# You must not change the print on line 17 - we do want to print the children’s ages. +# (Feel free to invent the ages of Imran’s children.) \ No newline at end of file diff --git a/sprint-5-prep-exercises/exercise_10.py b/sprint-5-prep-exercises/exercise_10.py new file mode 100644 index 000000000..51524115b --- /dev/null +++ b/sprint-5-prep-exercises/exercise_10.py @@ -0,0 +1,59 @@ +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}") + +# Try changing the type annotation of Person.preferred_operating_system from str to List[str]. +# Run mypy on the code. + +# exercise_10.py:29: error: Argument "preferred_operating_system" to "Person" has incompatible type "str"; expected "list[str]" [arg-type] +# exercise_10.py:30: error: Argument "preferred_operating_system" to "Person" has incompatible type "str"; expected "list[str]" [arg-type] + + +# It tells us different places that our code is now wrong, because we’re passing values of the wrong type. +# We probably also want to rename our field - lists are plural. Rename the field to preferred_operating_systems. +# Run mypy again. + +# exercise_10.py:23: error: "Person" has no attribute "preferred_operating_system"; maybe "preferred_operating_systems"? [attr-defined] +# exercise_10.py:29: error: Unexpected keyword argument "preferred_operating_system" for "Person"; did you mean "preferred_operating_systems"? [call-arg] +# exercise_10.py:30: error: Unexpected keyword argument "preferred_operating_system" for "Person"; did you mean "preferred_operating_systems"? [call-arg] + +# Fix all of the places that mypy tells you need changing. Make sure the program works as you’d expect. \ No newline at end of file diff --git a/sprint-5-prep-exercises/exercise_11.py b/sprint-5-prep-exercises/exercise_11.py new file mode 100644 index 000000000..88a023ba9 --- /dev/null +++ b/sprint-5-prep-exercises/exercise_11.py @@ -0,0 +1,88 @@ +# Write a program which: + +# Already has a list of Laptops that a library has to lend out. +# Accepts user input to create a new Person - it should use the input function to read a person’s name, age, and preferred operating system. +# Tells the user how many laptops the library has that have that operating system. +# If there is an operating system that has more laptops available, tells the user that if they’re willing to accept that operating system they’re more likely to get a laptop. + +# You should convert the age and preferred operating system input from the user into more constrained types as quickly as possible, +# and should output errors to stderr and terminate the program with a non-zero exit code if the user input bad values. + +# ----- Imports +from dataclasses import dataclass +from enum import Enum +import sys + + # ----- Classes and Enums +class OperatingSystem(Enum): + MACOS = "macOS" + ARCH = "Arch Linux" + UBUNTU = "Ubuntu" + WINDOWS = "Windows" + +@dataclass(frozen=True) +class Person: + name: str + age: int + preferred_operating_system: OperatingSystem + +@dataclass(frozen=True) +class Laptop: + id: int + operating_system: OperatingSystem + + # ----- Functions +def count_laptops(laptops: list[Laptop], laptop_counts: dict[str, int]): + for laptop in laptops: + laptop_counts[laptop.operating_system] += 1 + +def check_laptop_abundance(user: Person, laptop_counts: dict[OperatingSystem, int]): + abundant_laptops = [] + for laptop in laptop_counts: + if laptop_counts[laptop] > laptop_counts[user.preferred_operating_system]: + abundant_laptops.append(laptop.value) + if len(abundant_laptops) > 0: + print("\nIf you are willing to accept another operating system you may get a laptop sooner.") + print("We have more laptops available with the following OS:") + for laptop in abundant_laptops: + print(laptop) + + # ----- Data and Constants +laptops = [ + Laptop(id=1, operating_system=OperatingSystem.ARCH), + Laptop(id=2, operating_system=OperatingSystem.ARCH), + Laptop(id=3, operating_system=OperatingSystem.UBUNTU), + Laptop(id=4, operating_system=OperatingSystem.UBUNTU), + Laptop(id=5, operating_system=OperatingSystem.UBUNTU), + Laptop(id=6, operating_system=OperatingSystem.UBUNTU), + Laptop(id=7, operating_system=OperatingSystem.MACOS), + Laptop(id=8, operating_system=OperatingSystem.MACOS), +] +laptop_counts = { + OperatingSystem.MACOS: 0, + OperatingSystem.ARCH: 0, + OperatingSystem.UBUNTU: 0, + OperatingSystem.WINDOWS: 0 +} + + # ----- Script +user_name = input("Please enter your full name:\n") +user_age_str = input("Please enter your age:\n") + +try: + user_age = int(user_age_str) +except ValueError: + sys.exit("Error: Age should be a number.") + +user_operating_system_str = input("Please enter your preferred operating system (options: ARCH, UBUNTU, MACOS, WINDOWS):\n") + +if user_operating_system_str not in OperatingSystem.__members__: + sys.exit("Error: Operating system should be written in all caps from given options.") +else: user_operating_system = OperatingSystem[user_operating_system_str] + +user = Person(user_name, user_age, user_operating_system) + +count_laptops(laptops, laptop_counts) +print(f"\nThe number of available laptops with {user_operating_system.value} is: {laptop_counts[user_operating_system]}") + +check_laptop_abundance(user, laptop_counts) \ No newline at end of file diff --git a/sprint-5-prep-exercises/exercise_12.py b/sprint-5-prep-exercises/exercise_12.py new file mode 100644 index 000000000..8ab0c3454 --- /dev/null +++ b/sprint-5-prep-exercises/exercise_12.py @@ -0,0 +1,81 @@ +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") +# Prediction: Creates instance of Child class with first name "Elizaveta" and last name "Alekseeva". +print(person1) +print(person1.first_name) +print(person1.last_name) +# Outcome: As expected. + + +print(person1.get_name()) +# Prediction: Child inherits Parent methods, therefore calls get_name method: "Elizaveta Alekseeva" +# Outcome: As expected. + +print(person1.get_full_name()) +# Prediction: Calls get_full_name on child, no previous names: "Elizaveta Alekseeva" +# Outcome: As expected. + +person1.change_last_name("Tyurina") +# Prediction: Changes last_name to "Tyurina", and previous_last_names to ["Alekseeva"], returns nothing +print(person1.last_name) +print(person1.previous_last_names) +# Outcome: As expected. + +print(person1.get_name()) +# Prediction: Child inherits Parent methods, therefore calls get_name method: "Elizaveta Alekseeva" +# Outcome: As expected. + +print(person1.get_full_name()) +# Prediction: Returns first_name last_name (née previous_last_names[0]) (original last name) +# ""Elizaveta Tyurina (née Alekseeva)" +# Outcome: As expected. + +person2 = Parent("Elizaveta", "Alekseeva") +# Prediction: Creates instance of Parent class with first name "Elizaveta" and last name "Alekseeva". +print(person2) +print(person2.first_name) +print(person2.last_name) +# Outcome: As expected. + +print(person2.get_name()) +# Prediction: Calls get_name method: "Elizaveta Alekseeva" +# Outcome: As expected. + +# print(person2.get_full_name()) +# Prediction: Parent instance has not access to Child methods, will error that there is no method of get_full_name. +# Outcome: AttributeError: 'Parent' object has no attribute 'get_full_name' + +# person2.change_last_name("Tyurina") +# Prediction: Parent instance has not access to Child methods, will error that there is no method of change_last_name. +# Outcome: AttributeError: 'Parent' object has no attribute 'change_last_name' + +print(person2.get_name()) +# Prediction: Calls get_name method: "Elizaveta Alekseeva" as name has not changed due to inability to call change_last_name +# Outcome: As expected. + +# print(person2.get_full_name()) +# Prediction: Parent instance has not access to Child methods, will error that there is no method of get_full_name. +# Outcome: AttributeError: 'Parent' object has no attribute 'get_full_name' \ No newline at end of file