From c8c82a9a3aa7cd1d1ec89b1a7972e20b7ae77cbc Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Thu, 6 Aug 2026 11:58:42 +0100 Subject: [PATCH 01/15] predict double --- sprint-5-exercises/predict_double.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 sprint-5-exercises/predict_double.py diff --git a/sprint-5-exercises/predict_double.py b/sprint-5-exercises/predict_double.py new file mode 100644 index 000000000..5e7f971bf --- /dev/null +++ b/sprint-5-exercises/predict_double.py @@ -0,0 +1,17 @@ +def half(value): + return value / 2 + +def double(value): + return value * 2 + +def second(value): + return value[1] + + +# predict what double("22") will do + +print(double("22")) + +# I predict that the function will return "2222", as the * operator is overloaded in python. +# So that if a number is given, it performs the arithmetic operation, but if a string is given it just repeats +# the string 2 times \ No newline at end of file From 1ffae146b075fd56a00e45c61727939ba317bcc7 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Thu, 6 Aug 2026 12:02:51 +0100 Subject: [PATCH 02/15] fix double --- sprint-5-exercises/fix_double.py | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 sprint-5-exercises/fix_double.py diff --git a/sprint-5-exercises/fix_double.py b/sprint-5-exercises/fix_double.py new file mode 100644 index 000000000..4f2077339 --- /dev/null +++ b/sprint-5-exercises/fix_double.py @@ -0,0 +1,7 @@ +def double(number): + # return number * 3 + return number * 2. # the fix + +print(double(10)) + +# bug: function is called double, but returns tripple of what is given as input. \ No newline at end of file From bb9228947c6be0d8d7f8ff421338bd4f2aaac2f1 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Thu, 6 Aug 2026 17:19:28 +0100 Subject: [PATCH 03/15] Add type annotation to bank account --- .../bank_account_annotations.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 sprint-5-exercises/bank_account_annotations.py diff --git a/sprint-5-exercises/bank_account_annotations.py b/sprint-5-exercises/bank_account_annotations.py new file mode 100644 index 000000000..caffd5a27 --- /dev/null +++ b/sprint-5-exercises/bank_account_annotations.py @@ -0,0 +1,33 @@ +from typing import Dict + +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, +} + +# the amount is int pence not float pounds +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 From daa4e3f03ec9667814eb493028897ee57557f74c Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Thu, 6 Aug 2026 17:27:00 +0100 Subject: [PATCH 04/15] explain mypy errors in Person class file --- sprint-5-exercises/person_class_errors.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 sprint-5-exercises/person_class_errors.py diff --git a/sprint-5-exercises/person_class_errors.py b/sprint-5-exercises/person_class_errors.py new file mode 100644 index 000000000..355b09ab3 --- /dev/null +++ b/sprint-5-exercises/person_class_errors.py @@ -0,0 +1,23 @@ +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) + +# Understand the errors from running mypy on this code + +# Person_class_errors.py:9: error: "Person" has no attribute "address" [attr-defined] +# Because there is type definiton in the constructor of the Person class, mypy checks whether +# the imran object has an address attribute, and finds that it does not. + +# Person_class_errors.py:13: error: "Person" has no attribute "address" [attr-defined] +# Same with eliza. It is a Person type object, without an address property, code attempts to +# print in line 13. \ No newline at end of file From 75ccf6d387cceb394146333fb8fc7963537fd1f2 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Thu, 6 Aug 2026 17:33:33 +0100 Subject: [PATCH 05/15] is_adult type check --- sprint-5-exercises/add_is_adult.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 sprint-5-exercises/add_is_adult.py diff --git a/sprint-5-exercises/add_is_adult.py b/sprint-5-exercises/add_is_adult.py new file mode 100644 index 000000000..70ab719ae --- /dev/null +++ b/sprint-5-exercises/add_is_adult.py @@ -0,0 +1,29 @@ + + +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) + +def is_adult(person: Person) -> bool: + return person.age >= 18 + +print(is_adult(imran)) + +def is_developer(person: Person) -> bool: + return person.is_developer + +print(is_developer(imran)) + +# As expected, there is an error because the is_developer attribute is not present in the Person class. \ No newline at end of file From 3f15834057b83e37d7928548a18679e7f5e4b4f6 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Thu, 6 Aug 2026 18:19:59 +0100 Subject: [PATCH 06/15] advantages of methods --- .../advantage_of_using_methods.txt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 sprint-5-exercises/advantage_of_using_methods.txt diff --git a/sprint-5-exercises/advantage_of_using_methods.txt b/sprint-5-exercises/advantage_of_using_methods.txt new file mode 100644 index 000000000..299f8a950 --- /dev/null +++ b/sprint-5-exercises/advantage_of_using_methods.txt @@ -0,0 +1,16 @@ +Encapsulation: +Data and methods are packaged together to form one cohesive unit. +This allows great control of access and modification of the data, +presenting an interface to the user, and hiding the implementation details. + +The class/object can impose rules on access and modification. E.g. balance can't +be negative. + +Implementation can also be changed without breaking the interface which +should be reliable and consistent over time. + +Ease of use: +Makes it easier for users of the data, as they only need to reason about +the interface, not the implementation details. E.g. methods that operate on an object +can be easily with the dot notation and IDE autocomplete. + From 51aa7d639536b43c7be6201f4c52a26e8b6e99bd Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Thu, 6 Aug 2026 20:12:32 +0100 Subject: [PATCH 07/15] datetime birthday --- sprint-5-exercises/person_datetime.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 sprint-5-exercises/person_datetime.py diff --git a/sprint-5-exercises/person_datetime.py b/sprint-5-exercises/person_datetime.py new file mode 100644 index 000000000..9e0409593 --- /dev/null +++ b/sprint-5-exercises/person_datetime.py @@ -0,0 +1,18 @@ +# modify to use datetime.date to take in a date of birth +# store in a field instead of age +import datetime as dt + +class Person: + def __init__(self, name: str, birthdate: dt.date, preferred_operating_system: str): + self.name = name + self.birthdate = birthdate + self.preferred_operating_system = preferred_operating_system + self.birthdate = birthdate + + def is_adult(self) -> bool: + today = dt.date.today() + print(today) + return today >= dt.date(self.birthdate.year +18, self.birthdate.month, self.birthdate.day) + +imran = Person("Imran", dt.date(2008,8,6), "Ubuntu") +print(imran.is_adult()) \ No newline at end of file From 9044ad56894fab69c406f502c05b5efbbb91f49b Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Thu, 6 Aug 2026 20:26:33 +0100 Subject: [PATCH 08/15] account for leap year --- sprint-5-exercises/person_datetime.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/sprint-5-exercises/person_datetime.py b/sprint-5-exercises/person_datetime.py index 9e0409593..5efbf1aaa 100644 --- a/sprint-5-exercises/person_datetime.py +++ b/sprint-5-exercises/person_datetime.py @@ -11,7 +11,18 @@ def __init__(self, name: str, birthdate: dt.date, preferred_operating_system: st def is_adult(self) -> bool: today = dt.date.today() - print(today) + years = today.year - self.birthdate.year + # python does a lexicographical comparison of the elements in the tuples + # only checks the days if the months are equal + + had_birthday_this_year = (today.month, today.day) >= (self.birthdate.month, self.birthdate.day) + age = years if had_birthday_this_year else years - 1 + return age >= 18 + + # note: the above is necessary because with my old version, if the original birthday is on feb 29 + # then it would try to create a new date of feb 29 on a non-leap year and crash + + return today >= dt.date(self.birthdate.year +18, self.birthdate.month, self.birthdate.day) imran = Person("Imran", dt.date(2008,8,6), "Ubuntu") From 6f51289d47f75a4cfae85479c6269030e49cdf8e Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Thu, 6 Aug 2026 20:40:34 +0100 Subject: [PATCH 09/15] remove dead code --- sprint-5-exercises/person_datetime.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/sprint-5-exercises/person_datetime.py b/sprint-5-exercises/person_datetime.py index 5efbf1aaa..878235bef 100644 --- a/sprint-5-exercises/person_datetime.py +++ b/sprint-5-exercises/person_datetime.py @@ -22,8 +22,5 @@ def is_adult(self) -> bool: # note: the above is necessary because with my old version, if the original birthday is on feb 29 # then it would try to create a new date of feb 29 on a non-leap year and crash - - return today >= dt.date(self.birthdate.year +18, self.birthdate.month, self.birthdate.day) - imran = Person("Imran", dt.date(2008,8,6), "Ubuntu") print(imran.is_adult()) \ No newline at end of file From cf257081b200c22071c92a461610fa3fa6b2d9c5 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Thu, 6 Aug 2026 20:42:43 +0100 Subject: [PATCH 10/15] convert Person to dataclass --- sprint-5-exercises/dataclass_person.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 sprint-5-exercises/dataclass_person.py diff --git a/sprint-5-exercises/dataclass_person.py b/sprint-5-exercises/dataclass_person.py new file mode 100644 index 000000000..a3d1573fb --- /dev/null +++ b/sprint-5-exercises/dataclass_person.py @@ -0,0 +1,25 @@ +# convert person class to a dataclass +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() + years = today.year - self.birthdate.year + # python does a lexicographical comparison of the elements in the tuples + # only checks the days if the months are equal + + had_birthday_this_year = (today.month, today.day) >= (self.birthdate.month, self.birthdate.day) + age = years if had_birthday_this_year else years - 1 + return age >= 18 + + # note: the above is necessary because with my old version, if the original birthday is on feb 29 + # then it would try to create a new date of feb 29 on a non-leap year and crash + +imran = Person("Imran", dt.date(2009,8,6), "Ubuntu") +print(imran.is_adult()) \ No newline at end of file From ca600fa1a00de6df0285ef1d566adf471bd722a7 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Thu, 6 Aug 2026 20:49:44 +0100 Subject: [PATCH 11/15] generics - children age --- sprint-5-exercises/generics.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 sprint-5-exercises/generics.py diff --git a/sprint-5-exercises/generics.py b/sprint-5-exercises/generics.py new file mode 100644 index 000000000..5aae0545a --- /dev/null +++ b/sprint-5-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=12) +aisha = Person(name="Aisha", children=[], age=15) + +imran = Person(name="Imran", children=[fatma, aisha], age=40) + +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 From 6a8dc0ca82615ee4f9372506b16f38e87ed6a931 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Thu, 6 Aug 2026 21:28:43 +0100 Subject: [PATCH 12/15] refactor: latop preferences is a list --- sprint-5-exercises/type_guided_refactoring.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 sprint-5-exercises/type_guided_refactoring.py diff --git a/sprint-5-exercises/type_guided_refactoring.py b/sprint-5-exercises/type_guided_refactoring.py new file mode 100644 index 000000000..5ba918a78 --- /dev/null +++ b/sprint-5-exercises/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 = [] + 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", "Arch Linux"]), + Person(name="Eliza", age=34, preferred_operating_systems=["Arch Linux", "macOs"]), +] + +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}") \ No newline at end of file From d03702f34ef9641db333adb40565b6253040f2ef Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Thu, 6 Aug 2026 22:29:09 +0100 Subject: [PATCH 13/15] create valid user from input --- sprint-5-exercises/laptop_enums.py | 88 ++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 sprint-5-exercises/laptop_enums.py diff --git a/sprint-5-exercises/laptop_enums.py b/sprint-5-exercises/laptop_enums.py new file mode 100644 index 000000000..adf8be9ff --- /dev/null +++ b/sprint-5-exercises/laptop_enums.py @@ -0,0 +1,88 @@ +from dataclasses import dataclass +from enum import Enum +from typing import List, Optional + +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 + + +def find_possible_laptops(laptops: List[Laptop], person: Person) -> List[Laptop]: + possible_laptops = [] + for laptop in laptops: + if laptop.operating_system == person.preferred_operating_system: + possible_laptops.append(laptop) + return possible_laptops + + +# people = [ +# Person(name="Imran", age=22, preferred_operating_system=OperatingSystem.UBUNTU), +# Person(name="Eliza", age=34, preferred_operating_system=OperatingSystem.ARCH), +# ] + +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), +] + + +# take input to create a new Person, validate the input to ensure the person data can be used to create a valid person + +# loops forever until alphabetic string provided +def person_name_input() -> str: + name = input("Enter your first name: ") + while True: + if (name.isalpha()): + return name + name = input("invalid first name, please enter only letters: ") + +# loops forever until numeric input is provided +def person_age_input() -> int: + age = input("Enter your age: ") + while True: + if (age.isnumeric()): + return int(age) + age = input("Invalid age, please enter only integer value: ") + +# loops forever until a valid OS is chosen +def preferred_os_input() -> OperatingSystem: + os_options = [member.name for member in OperatingSystem] + os_choice = input(f"Enter your preferred laptop from {os_options}: ").strip().upper() + + while True: + if (os_choice in os_options): + return OperatingSystem[os_choice] + os_choice = input(f"Invalid choice, check spelling and spaces. choices: {os_options}: ").strip().upper() + + + + +# run a while loop to act as an interactive menu, in which user input is taken step by step +print(f"Welcome to the CYF library. There are {len(laptops)} laptops available!") +print("Enter your details to begin") +while True: + name = person_name_input() + age = person_age_input() + prefered_os = preferred_os_input() + + person: Person = Person(name, age, prefered_os) + print(person) + break \ No newline at end of file From 5eb94f11cf2517b9e53f35f406762992a96db806 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Thu, 6 Aug 2026 23:25:38 +0100 Subject: [PATCH 14/15] show user laptop choices --- sprint-5-exercises/laptop_enums.py | 37 +++++++++++++++++++----------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/sprint-5-exercises/laptop_enums.py b/sprint-5-exercises/laptop_enums.py index adf8be9ff..400b9c5c9 100644 --- a/sprint-5-exercises/laptop_enums.py +++ b/sprint-5-exercises/laptop_enums.py @@ -1,6 +1,7 @@ from dataclasses import dataclass from enum import Enum -from typing import List, Optional +from typing import List +from collections import Counter class OperatingSystem(Enum): MACOS = "macOS" @@ -44,7 +45,10 @@ def find_possible_laptops(laptops: List[Laptop], person: Person) -> List[Laptop] ] -# take input to create a new Person, validate the input to ensure the person data can be used to create a valid person +# take input (name, age, preferred os), create Person object +# show them how many laptops with their chosen OS are available +# if there is a different os with more laptops, tell user they are more likely to get a laptop +# if they choose that os # loops forever until alphabetic string provided def person_name_input() -> str: @@ -73,16 +77,23 @@ def preferred_os_input() -> OperatingSystem: os_choice = input(f"Invalid choice, check spelling and spaces. choices: {os_options}: ").strip().upper() +print(f"Welcome to the CYF library. Enter your details to begin") - -# run a while loop to act as an interactive menu, in which user input is taken step by step -print(f"Welcome to the CYF library. There are {len(laptops)} laptops available!") -print("Enter your details to begin") -while True: - name = person_name_input() - age = person_age_input() - prefered_os = preferred_os_input() +name = person_name_input() +age = person_age_input() +prefered_os = preferred_os_input() - person: Person = Person(name, age, prefered_os) - print(person) - break \ No newline at end of file +person: Person = Person(name, age, prefered_os) + +possible_laptops = find_possible_laptops(laptops, person) + +print(f"There are {len(possible_laptops)} laptops with your preferred OS.") + +# keep only non-preferred OS, and then see if there there is an OS with more laptops available +non_preferred_os = filter(lambda x: x.operating_system != person.preferred_operating_system, laptops) + +counter = Counter(laptop.operating_system for laptop in non_preferred_os) +most_common_os, count = counter.most_common(1)[0] + +if (count > len(possible_laptops)): + print(f"there are {count} latops with {most_common_os.name} operating system. You are more likely to get a laptop if you choose {most_common_os.name} ") \ No newline at end of file From 2c28a273cd10fd7f4420297d8bc81a09c2986a76 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Fri, 7 Aug 2026 00:00:43 +0100 Subject: [PATCH 15/15] inheritance predictions --- sprint-5-exercises/inheritance_check.py | 44 +++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 sprint-5-exercises/inheritance_check.py diff --git a/sprint-5-exercises/inheritance_check.py b/sprint-5-exercises/inheritance_check.py new file mode 100644 index 000000000..bb9e382b0 --- /dev/null +++ b/sprint-5-exercises/inheritance_check.py @@ -0,0 +1,44 @@ + +# parent class has two string fields, first_name, last_name +# a method that return a string which joins the two names with a space +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}" + + +# extends parent class +# add ability to change last name, store previous last names in a list +# a method that prints first and last name as well as the original last name of Child +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") +print(person1.get_name()) # inherit from Parent class, output = "Elizaveta Alekseeva" +print(person1.get_full_name()) # method of Child class, output = "Elizaveta Alekseeva" no previous surname +person1.change_last_name("Tyurina") # changes last name of person1, adds "Alekseeva" to previous names list +print(person1.get_name()) # last name has changed, output = "Elizaveta Tyurina" +print(person1.get_full_name()) # includes maiden name, output = "Elizaveta Tyurina (née Alekseeva)" + +person2 = Parent("Elizaveta", "Alekseeva") +print(person2.get_name()) # output = "Elizaveta Alekseeva" +print(person2.get_full_name()) # AttrbuteError - the Parent class does not have get_full_name() method +person2.change_last_name("Tyurina") # same again +print(person2.get_name()) # no problems, same as line 40 +print(person2.get_full_name()) # again, no get_full_name() method in this Parent class. Same as line 41 \ No newline at end of file