From dcc6ad5861f55199858173f83237fdeb2af46c2e Mon Sep 17 00:00:00 2001 From: l Date: Wed, 9 Sep 2026 15:56:45 +0100 Subject: [PATCH 1/8] add first exercises --- sprint-5/01-predict.py | 40 +++++++++++++++++++++++++++++++++++++ sprint-5/02-playcomputer.js | 27 +++++++++++++++++++++++++ sprint-5/03-fix.py | 9 +++++++++ sprint-5/04-addmypy.py | 39 ++++++++++++++++++++++++++++++++++++ sprint-5/05-explain.py | 20 +++++++++++++++++++ sprint-5/README.md | 11 ++++++++++ sprint-5/requirements.txt | 1 + 7 files changed, 147 insertions(+) create mode 100644 sprint-5/01-predict.py create mode 100644 sprint-5/02-playcomputer.js create mode 100644 sprint-5/03-fix.py create mode 100644 sprint-5/04-addmypy.py create mode 100644 sprint-5/05-explain.py create mode 100644 sprint-5/README.md create mode 100644 sprint-5/requirements.txt diff --git a/sprint-5/01-predict.py b/sprint-5/01-predict.py new file mode 100644 index 000000000..fca6e4b1a --- /dev/null +++ b/sprint-5/01-predict.py @@ -0,0 +1,40 @@ +def half(value): + return value / 2 + +def double(value): + return value * 2 + +def second(value): + return value[1] + +# 1. Predict what you think will happen with each of the following functions +# 2. Then, test it, and explain in your own words what is actually happening and why +# (feel free to comment out lines if you think they cause errors or crashes while testing) + +print(half(22)) +# Prediction: +# What actually happens and why: + +print(half("22")) +# Prediction: +# What actually happens and why: + +print(double(22)) +# Prediction: +# What actually happens and why: + +print(double("22")) +# Prediction: +# What actually happens and why: + +print(second(22)) +# Prediction: +# What actually happens and why: + +print(second(0x16)) +# Prediction: +# What actually happens and why: + +print(second("22")) +# Prediction: +# What actually happens and why: diff --git a/sprint-5/02-playcomputer.js b/sprint-5/02-playcomputer.js new file mode 100644 index 000000000..2fad07c1c --- /dev/null +++ b/sprint-5/02-playcomputer.js @@ -0,0 +1,27 @@ +import process from "node:process"; +import readline from "node:readline"; + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, +}); + +rl.question("What URL should we fetch?\n> ", async (url) => { + const response = await fetch(url); + if (!response.ok) { + if (response.body.toLowerCase().includes("permission")) { + console.error("You didn't have permission to get that URL"); + } else { + console.error(`The request failed - body: ${response.body}`); + } + process.exit(1); + } + + const contents = await response.json(); + + console.log(contents); + + rl.close(); +}); + +// Task: Leave a comment on any line that you can see has some errors explaining what you think the problem is diff --git a/sprint-5/03-fix.py b/sprint-5/03-fix.py new file mode 100644 index 000000000..477b11f43 --- /dev/null +++ b/sprint-5/03-fix.py @@ -0,0 +1,9 @@ +def double(number): + return number * 3 + +print(double(10)) + +# Task: +# What is the bug here? +# How could you fix it? +# Are there multiple possible ways to fix it? diff --git a/sprint-5/04-addmypy.py b/sprint-5/04-addmypy.py new file mode 100644 index 000000000..e1abe7c45 --- /dev/null +++ b/sprint-5/04-addmypy.py @@ -0,0 +1,39 @@ +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("Tobi", 9.13) +open_account("Olya", "£7.13") + +total_pence = sum_balances(balances) +total_string = format_pence_as_str(total_pence) + +print(f"The bank accounts total {total_string}") + +# TASK +# This code contains bugs related to types. They are bugs mypy can catch. +# +# 1. Read this code to understand what it's trying to do. +# 2. Install and set up mypy in a python virtual environment +# 3. Add type annotations everywhere appropriate +# 4. Run `mypy 04-addmypy.py`, and fix any errors +# 5. When you're confident all of the type annotations are correct, and the bugs are fixed, run the code and check it works. diff --git a/sprint-5/05-explain.py b/sprint-5/05-explain.py new file mode 100644 index 000000000..91cff19ad --- /dev/null +++ b/sprint-5/05-explain.py @@ -0,0 +1,20 @@ +imran = { + "name": "Imran", + "age": 22, + "preferred_operating_system": "Ubuntu", +} + +eliza = { + "name": "Eliza", + "age": 34, + "preferred_operating_system": "Arch Linux", +} + + +print(imran["name"]) +print(imran["address"]) + +# Task: +# Try running mypy on this file and see what happens +# Then try executing the file and see what happens +# What is happening here? diff --git a/sprint-5/README.md b/sprint-5/README.md new file mode 100644 index 000000000..ffd9d59d8 --- /dev/null +++ b/sprint-5/README.md @@ -0,0 +1,11 @@ +# Sprint 5 + +For this task, read through the prep material for sprint 5. + +Sprint 5 is all about types in prorgamming. + +This directory has some example code and exercises the prep will refer to. + +As you read through the prep, complete the tasks here. + +Then submit as a PR once you are finished. diff --git a/sprint-5/requirements.txt b/sprint-5/requirements.txt new file mode 100644 index 000000000..f0aa93ac8 --- /dev/null +++ b/sprint-5/requirements.txt @@ -0,0 +1 @@ +mypy From 31056a42d1b1514408041ca925cee4e7408b44b8 Mon Sep 17 00:00:00 2001 From: l Date: Wed, 16 Sep 2026 16:02:30 +0100 Subject: [PATCH 2/8] update classes --- sprint-5/{05-explain.py => 05-predict.py} | 4 ++-- sprint-5/06-classes.py | 28 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) rename sprint-5/{05-explain.py => 05-predict.py} (73%) create mode 100644 sprint-5/06-classes.py diff --git a/sprint-5/05-explain.py b/sprint-5/05-predict.py similarity index 73% rename from sprint-5/05-explain.py rename to sprint-5/05-predict.py index 91cff19ad..cc15650d2 100644 --- a/sprint-5/05-explain.py +++ b/sprint-5/05-predict.py @@ -16,5 +16,5 @@ # Task: # Try running mypy on this file and see what happens -# Then try executing the file and see what happens -# What is happening here? +# Predict what do you think will happen when you run this task? +# Can you explain what actualy happens? diff --git a/sprint-5/06-classes.py b/sprint-5/06-classes.py new file mode 100644 index 000000000..ff335765d --- /dev/null +++ b/sprint-5/06-classes.py @@ -0,0 +1,28 @@ +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) + +# Task 6.1: +# Run mypy on this code +# Fix the code so there are no errors in mypy or when it runs + +# Task 6.2: +# Create a new function in this file called likes_apple +# It should take a person as parameter +# It returns true if the preferred operating system is "macOS" or "iOS" +# It should return false for any other preferred os +# Add all the appropriate type annotations and test it has no errors in mypy + +# Task 6.3: +# Compare objects and classes +# What are some advantages and disadvantages of each? From 5709d46c1bee2ea742830dd32fe5137b1a60616a Mon Sep 17 00:00:00 2001 From: l Date: Wed, 16 Sep 2026 16:53:46 +0100 Subject: [PATCH 3/8] update methods tasks --- sprint-5/07-methods.txt | 4 ++++ sprint-5/08-implement.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 sprint-5/07-methods.txt create mode 100644 sprint-5/08-implement.py diff --git a/sprint-5/07-methods.txt b/sprint-5/07-methods.txt new file mode 100644 index 000000000..28098862a --- /dev/null +++ b/sprint-5/07-methods.txt @@ -0,0 +1,4 @@ +Answer the following question: +What is the difference between a method and a function? +Can you give some advantages of methods over functions? + diff --git a/sprint-5/08-implement.py b/sprint-5/08-implement.py new file mode 100644 index 000000000..3b4f6540f --- /dev/null +++ b/sprint-5/08-implement.py @@ -0,0 +1,30 @@ +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.is_adult()) + +# Task: +# 1. Add the `drivers_license_check` free function and the `is_adult` method into the code +# Make sure your code currently gives the expected output. +# +# 2. Change the `Person` class to take a date of birth +# Use the standard library's `datetime.date` class +# https://docs.python.org/3/library/datetime.html#datetime.date +# Store the `date of birth` in a field instead of `age` (it should be a `str`) +# +# 3. Try to run your code +# How does this change break your code. +# What kind of error do you get? +# Is it helpful in identifying where your next change needs to be? +# Type your thoughts here: +# +# +# +# +# 4. Update the `is_adult` method so the error is fixed. +# Using the `drivers_license_check` function check everything runs as expected + From 4c4928b4b17a949d42cae1518313bef529902edc Mon Sep 17 00:00:00 2001 From: l Date: Wed, 16 Sep 2026 17:04:54 +0100 Subject: [PATCH 4/8] new dataclass task from poonam --- sprint-5/09-dataclass.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 sprint-5/09-dataclass.py diff --git a/sprint-5/09-dataclass.py b/sprint-5/09-dataclass.py new file mode 100644 index 000000000..857ef3379 --- /dev/null +++ b/sprint-5/09-dataclass.py @@ -0,0 +1,12 @@ +# Task 9.1: +# Copy the code you have so far from task 08-implement.py +# Convert this to a dataclass +# Test that the dataclass works with mypy, with equality checks + +# Task 9.2: +# Add a greeting method that says "Hello, !" + +# Task 9.3: +# Read the @datatype documentation here: https://docs.python.org/3/library/dataclasses.html +# Explain what does `frozen=True` do to the class? +# What other options could you play around with and explore? Offer suggestions for any that would be useful here. From 259c684c0a6a5706eb1c7bb5309e9a9bee291e2f Mon Sep 17 00:00:00 2001 From: l Date: Wed, 23 Sep 2026 15:25:12 +0100 Subject: [PATCH 5/8] generic tasks --- sprint-5/10-predict.py | 30 ++++++++++++++++++++ sprint-5/11-fix.py | 63 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 sprint-5/10-predict.py create mode 100644 sprint-5/11-fix.py diff --git a/sprint-5/10-predict.py b/sprint-5/10-predict.py new file mode 100644 index 000000000..f4a639b52 --- /dev/null +++ b/sprint-5/10-predict.py @@ -0,0 +1,30 @@ +from dataclasses import dataclass + +@dataclass(frozen=True) +class Animal: + name: str + species: str + +@dataclass(frozen=True) +class Person: + name: str + age: int + +@dataclass(frozen=True) +class FamilyTree: + parent: Person + members: list + +pet = Animal(name="Gromit", species="Dog") +fatma = Person(name="Fatma", age=4) +aisha = Person(name="Aisha", age=6) +imran = Person(name="Imran", age=30) + +family = FamilyTree(parent=imran, members=[fatma, aisha, pet]) + +def print_family_tree(family: FamilyTree): + print(family.parent.name) + for child in family.members: + print(f"{child.name} ({child.age} years old)") + +print_family_tree(family) diff --git a/sprint-5/11-fix.py b/sprint-5/11-fix.py new file mode 100644 index 000000000..bbeefe60a --- /dev/null +++ b/sprint-5/11-fix.py @@ -0,0 +1,63 @@ +from dataclasses import dataclass +from typing import List + +@dataclass(frozen=True) +class Animal: + name: str + size: str + +@dataclass(frozen=True) +class Person: + name: str + age: int + +@dataclass(frozen=True) +class Tree[T]: + parent: T + children: List[T] + + def print_tree(self): + print(self.parent) + for child in self.children: + print(child) + +fatma = Person(name="Fatma", age=4) +aisha = Person(name="Aisha", age=6) +imran = Person(name="Imran", age=30) +family_tree = Tree[Person](parent=imran, children=[fatma, aisha]) + +cats = Animal(name="Cat", size="Small") +dogs = Animal(name="Dog", size="Medium") +mammals = Animal(name="Mammals", size="Variable") +species_tree = Tree[Animal](parent=mammals, children=[cats, dogs]) + +family_tree.print_tree() +species_tree.print_tree() + +# Task 11: +# Experiment with mypy and make sure that the family tree only takes `Person` types and the species tree only takes `Animal` types. +# +# We are going to add printing to the above code. +# +# Unlike our earlier example, we want to avoid having to create two +# separate looping methods to print out an entire tree for each datatype. +# So we have created a single looping function within Tree that will work for any datatype. +# +# Currently the `Tree.print_tree()` function doesn't do anything. +# +# Add some appropriate methods to each of the Animal and Person classes to allow it to work. +# +# **Stretch task** +# +# Think of another type of data that can be organised into a tree. +# +# Add a new class for this, instantiate some variables, and have the existing `Tree` class print it out. +# Here is an example of what should be printed out +''' +Imran (30 years old) +- Fatma (4 years old) +- Aisha (6 years old)) +Mammals (Variable size) +- Cat (Small size) +- Dog (Medium size) +''' From 451130c7679c753cebadbc44639e072ce077785e Mon Sep 17 00:00:00 2001 From: l Date: Wed, 23 Sep 2026 15:42:33 +0100 Subject: [PATCH 6/8] refactoring --- sprint-5/12-refactor.py | 57 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 sprint-5/12-refactor.py diff --git a/sprint-5/12-refactor.py b/sprint-5/12-refactor.py new file mode 100644 index 000000000..9bf8be375 --- /dev/null +++ b/sprint-5/12-refactor.py @@ -0,0 +1,57 @@ +from dataclasses import dataclass +from typing import List + +@dataclass(frozen=True) +class Person: + name: str + age: int + preferred_operating_system: 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_system: + possible_laptops.append(laptop) + return possible_laptops + + +people = [ + Person(name="Imran", age=22, preferred_operating_system="Ubuntu"), + Person(name="Eliza", age=34, preferred_operating_system="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}") + +# Task 12 +#Try changing the type annotation of `Person.preferred_operating_system` from `str` to `List[str]`. +# +#Run mypy on the code. +# +#It tells us different places that our code is now wrong. Fix it to remov eany errors. +# +#Now we changed the types, we probably also want to _rename_ our fields to something appropriate. +# +#Run mypy again. +# +#Fix all of the places that mypy tells you need changing. +# +#Then, make sure the program works as you'd expect. From 880af4c3ffbf49b41759c8ca8db576858410bd31 Mon Sep 17 00:00:00 2001 From: l Date: Wed, 23 Sep 2026 16:09:30 +0100 Subject: [PATCH 7/8] enums --- sprint-5/13-implement.py | 55 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 sprint-5/13-implement.py diff --git a/sprint-5/13-implement.py b/sprint-5/13-implement.py new file mode 100644 index 000000000..61bf1fbfc --- /dev/null +++ b/sprint-5/13-implement.py @@ -0,0 +1,55 @@ +from dataclasses import dataclass +from typing import List + +@dataclass(frozen=True) +class Person: + name: str + age: int + preferred_operating_system: 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_system: + possible_laptops.append(laptop) + return possible_laptops + + +people = [ + Person(name="Imran", age=22, preferred_operating_system="ubuntu"), + Person(name="Eliza", age=34, preferred_operating_system="arch"), +] + +laptops = [ + Laptop(id=1, manufacturer="Dell", model="XPS", screen_size_in_inches=13, operating_system="arch"), + 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}") + +# Task 13 +# It currently handles operating systems as strings. +# +# Refactor the code to use enums for operating systems. +# +# Check with mypy and test it to ensure the program still works correctly. +# +# Replace the list of existing people with the `input` function to read a person's name, age, and preferred operating system. +# +# Make sure your implementation has a good user experience, and properly validates the inputs, mapping an OS to one of the enum values. +# +# If an operating system can't be matched at all, your script should handle it appropriately and not crash. From d88743d6e0746c491bce0b568a92918d0c736d7e Mon Sep 17 00:00:00 2001 From: l Date: Wed, 23 Sep 2026 16:38:32 +0100 Subject: [PATCH 8/8] inheritance --- sprint-5/14-analyse.py | 82 +++++++++++++++++++++++++++++++++++++ sprint-5/15-playcomputer.py | 42 +++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 sprint-5/14-analyse.py create mode 100644 sprint-5/15-playcomputer.py diff --git a/sprint-5/14-analyse.py b/sprint-5/14-analyse.py new file mode 100644 index 000000000..908db405e --- /dev/null +++ b/sprint-5/14-analyse.py @@ -0,0 +1,82 @@ +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()) # This doesn't work - the superclass doesn't define this method. + + +# Task 14 +# Try using this code and make sure you understand how it works and what it does +# +# Answer the following questions, writing your answers in the file, before checking the answers. +# +# Q1: If you know in advance you need to frequently access the largest item of the list, which class will be more efficient and why? +# +# Q2: If you know in advance you will be initialising many of them repeatedly, which class will be more efficient and why? +# diff --git a/sprint-5/15-playcomputer.py b/sprint-5/15-playcomputer.py new file mode 100644 index 000000000..0616c8761 --- /dev/null +++ b/sprint-5/15-playcomputer.py @@ -0,0 +1,42 @@ +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}" + +# Task: +# Play computer here +# Describe what is happening and why on each line below here +# If any lines cause errors, comment out the line and explain why the error happens + +person1 = Child("Elizaveta", "Alekseeva") +print(person1.get_name()) +print(person1.get_full_name()) +person1.change_last_name("Tyurina") +print(person1.get_name()) +print(person1.get_full_name()) + +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())