From f593c90b519718f9c95bb66d57a8433ef2c854e3 Mon Sep 17 00:00:00 2001 From: Geraldine-Edwards Date: Fri, 5 Dec 2025 16:43:35 +0000 Subject: [PATCH 1/7] initialise the laptop allocation file from the backlog --- .../laptop_allocaton.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 implement-laptop-allocation/laptop_allocaton.py diff --git a/implement-laptop-allocation/laptop_allocaton.py b/implement-laptop-allocation/laptop_allocaton.py new file mode 100644 index 000000000..9b9faf7c2 --- /dev/null +++ b/implement-laptop-allocation/laptop_allocaton.py @@ -0,0 +1,27 @@ +from dataclasses import dataclass +from enum import Enum +from typing import List + +class OperatingSystem(Enum): + MACOS = "macOS" + ARCH = "Arch Linux" + UBUNTU = "Ubuntu" + +@dataclass(frozen=True) +class Person: + name: str + age: int + # Sorted in order of preference, most preferred is first. + preferred_operating_system: List[OperatingSystem] + + +@dataclass(frozen=True) +class Laptop: + id: int + manufacturer: str + model: str + screen_size_in_inches: float + operating_system: OperatingSystem + + +def allocate_laptops(people: List[Person], laptops: List[Laptop]) -> Dict[Person, Laptop]: \ No newline at end of file From 84d03a00cfb3b7158263de5c3dfda2abc5db2a91 Mon Sep 17 00:00:00 2001 From: Geraldine-Edwards Date: Fri, 5 Dec 2025 22:26:52 +0000 Subject: [PATCH 2/7] Implement initial laptop allocation logic and user input handling --- .../laptop_allocaton.py | 85 ++++++++++++++++++- 1 file changed, 82 insertions(+), 3 deletions(-) diff --git a/implement-laptop-allocation/laptop_allocaton.py b/implement-laptop-allocation/laptop_allocaton.py index 9b9faf7c2..60e2db485 100644 --- a/implement-laptop-allocation/laptop_allocaton.py +++ b/implement-laptop-allocation/laptop_allocaton.py @@ -1,6 +1,7 @@ +import sys from dataclasses import dataclass from enum import Enum -from typing import List +from typing import List, Dict class OperatingSystem(Enum): MACOS = "macOS" @@ -12,7 +13,7 @@ class Person: name: str age: int # Sorted in order of preference, most preferred is first. - preferred_operating_system: List[OperatingSystem] + preferred_operating_systems: List[OperatingSystem] @dataclass(frozen=True) @@ -23,5 +24,83 @@ class Laptop: screen_size_in_inches: float operating_system: OperatingSystem +# In the prep, there was an exercise around finding possible laptops for a group of people. -def allocate_laptops(people: List[Person], laptops: List[Laptop]) -> Dict[Person, Laptop]: \ No newline at end of file +# Your exercise is to extend this to actually allocate laptops to the people. +# Every person should be allocated exactly one laptop. + +# If we define “sadness” as the number of places down in someone’s ranking the operating system the ended +# up with (i.e. if your preferences were [UBUNTU, ARCH, MACOS] and you were allocated a MACOS +# machine your sadness would be 2), we want to minimise the total sadness of all people. +# If we allocate someone a laptop with an operating system not in their preferred list, +# treat them as having a sadness of 100. + +laptops_list: List[Laptop] = [ + 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), + Laptop(id=5, manufacturer="Apple", model="MacBook Air", screen_size_in_inches=13, operating_system=OperatingSystem.MACOS), + Laptop(id=6, manufacturer="Lenovo", model="ThinkPad", screen_size_in_inches=14, operating_system=OperatingSystem.ARCH), + Laptop(id=7, manufacturer="Asus", model="ZenBook", screen_size_in_inches=13, operating_system=OperatingSystem.UBUNTU), + Laptop(id=8, manufacturer="HP", model="Spectre", screen_size_in_inches=14, operating_system=OperatingSystem.MACOS), + Laptop(id=9, manufacturer="Apple", model="MacBook Pro", screen_size_in_inches=16, operating_system=OperatingSystem.MACOS), +] + +people: List[Person] = [ + Person(name="Imran", age=18, preferred_operating_systems=[OperatingSystem.UBUNTU, OperatingSystem.ARCH]), + Person(name="Eliza", age=34, preferred_operating_systems=[OperatingSystem.ARCH, OperatingSystem.MACOS]), + Person(name="Luke", age=26, preferred_operating_systems=[OperatingSystem.MACOS, OperatingSystem.UBUNTU, OperatingSystem.ARCH]), + Person(name="Abby", age=30, preferred_operating_systems=[OperatingSystem.MACOS]), + Person(name="Ger", age=51, preferred_operating_systems=[OperatingSystem.UBUNTU, OperatingSystem.MACOS]), +] + +def user_prompt() -> Person: + try: + # strip() whitespace before processing (no need for str type here as input always returns a string) + name = input("Please enter your first name: ").strip() + if not name.isalpha(): + raise ValueError("Name must contain only alphabetic characters.") + + # strip() before converting to integer + age = int(input("Please enter your age: ").strip()) + minimum_age = 18 + if age < minimum_age: + raise ValueError("Age must be 18 or over.") + + + # define valid OS options + valid_os = [os.value for os in OperatingSystem] + + # prompt the user to enter OS preferences in order of preference (no need for str type here) + preferred_os = input(f"Please enter your preferred operating systems in order of preference, separated by commas (e.g., {', '.join(valid_os)}): ").strip() + + # split and validate the OS + preferred_os_list = [os.strip() for os in preferred_os.split(",")] + preferred_os_enum = [] + for os_name in preferred_os_list: + if os_name not in valid_os: + raise ValueError(f"Invalid operating system: {os_name}") + # convert to enum + preferred_os_enum.append(OperatingSystem(os_name)) + + return Person(name=name, age=age, preferred_operating_systems=preferred_os_enum) + + # throw an error and exit for invalid age and os input + except ValueError as error: + print(f"Invalid input: {error}", file=sys.stderr) + sys.exit(1) + + +def find_possible_laptops(available_laptops: List[Laptop], current_person: Person) -> List[Laptop]: + return [ + laptop for laptop in available_laptops + if laptop.operating_system in current_person.preferred_operating_systems + ] + +# allocated sequentially, in a first come first served order +def allocate_laptops_sequentially(people: List[Person], laptops: List[Laptop]) -> Dict[Person, Laptop]: + """ + Allocate laptops to people sequentially based on the order in the people list. + This approach respects the 'wait your turn' principle. + """ \ No newline at end of file From cf8959cc2f7478f7aad7df0da1f6b66dc7286a18 Mon Sep 17 00:00:00 2001 From: Geraldine-Edwards Date: Fri, 5 Dec 2025 23:59:59 +0000 Subject: [PATCH 3/7] Enhance laptop allocation logic with user input validation and sadness minimization --- .../laptop_allocaton.py | 78 +++++++++++++++---- 1 file changed, 65 insertions(+), 13 deletions(-) diff --git a/implement-laptop-allocation/laptop_allocaton.py b/implement-laptop-allocation/laptop_allocaton.py index 60e2db485..67e24a73b 100644 --- a/implement-laptop-allocation/laptop_allocaton.py +++ b/implement-laptop-allocation/laptop_allocaton.py @@ -4,20 +4,23 @@ from typing import List, Dict class OperatingSystem(Enum): + """enumeration of available operating systems.""" MACOS = "macOS" ARCH = "Arch Linux" UBUNTU = "Ubuntu" @dataclass(frozen=True) class Person: + """represents a person with a name, age, and their OS preferences.""" name: str age: int - # Sorted in order of preference, most preferred is first. + # listed in order of preference preferred_operating_systems: List[OperatingSystem] @dataclass(frozen=True) class Laptop: + """represents a laptop with specifications and an operating system.""" id: int manufacturer: str model: str @@ -41,10 +44,7 @@ class Laptop: 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), Laptop(id=5, manufacturer="Apple", model="MacBook Air", screen_size_in_inches=13, operating_system=OperatingSystem.MACOS), - Laptop(id=6, manufacturer="Lenovo", model="ThinkPad", screen_size_in_inches=14, operating_system=OperatingSystem.ARCH), - Laptop(id=7, manufacturer="Asus", model="ZenBook", screen_size_in_inches=13, operating_system=OperatingSystem.UBUNTU), - Laptop(id=8, manufacturer="HP", model="Spectre", screen_size_in_inches=14, operating_system=OperatingSystem.MACOS), - Laptop(id=9, manufacturer="Apple", model="MacBook Pro", screen_size_in_inches=16, operating_system=OperatingSystem.MACOS), + Laptop(id=6, manufacturer="HP", model="Spectre", screen_size_in_inches=14, operating_system=OperatingSystem.MACOS), ] people: List[Person] = [ @@ -56,7 +56,11 @@ class Laptop: ] def user_prompt() -> Person: + """ + prompt the user to input their details and preferred operating systems + """ try: + # strip() whitespace before processing (no need for str type here as input always returns a string) name = input("Please enter your first name: ").strip() if not name.isalpha(): @@ -76,7 +80,10 @@ def user_prompt() -> Person: preferred_os = input(f"Please enter your preferred operating systems in order of preference, separated by commas (e.g., {', '.join(valid_os)}): ").strip() # split and validate the OS - preferred_os_list = [os.strip() for os in preferred_os.split(",")] + preferred_os_list = [os.strip() for os in preferred_os.split(",") if os.strip()] + if not preferred_os_list: + raise ValueError("You must enter at least one operating system.") + preferred_os_enum = [] for os_name in preferred_os_list: if os_name not in valid_os: @@ -84,7 +91,7 @@ def user_prompt() -> Person: # convert to enum preferred_os_enum.append(OperatingSystem(os_name)) - return Person(name=name, age=age, preferred_operating_systems=preferred_os_enum) + return Person(name=name, age=age, preferred_operating_systems=tuple(preferred_os_enum)) # throw an error and exit for invalid age and os input except ValueError as error: @@ -93,14 +100,59 @@ def user_prompt() -> Person: def find_possible_laptops(available_laptops: List[Laptop], current_person: Person) -> List[Laptop]: + """ + find laptops that match a person's preferred operating systems. + """ return [ laptop for laptop in available_laptops if laptop.operating_system in current_person.preferred_operating_systems ] -# allocated sequentially, in a first come first served order -def allocate_laptops_sequentially(people: List[Person], laptops: List[Laptop]) -> Dict[Person, Laptop]: - """ - Allocate laptops to people sequentially based on the order in the people list. - This approach respects the 'wait your turn' principle. - """ \ No newline at end of file + +def allocate_laptops(people: List[Person], laptops: List[Laptop]) -> Dict[Person, Laptop]: + """ + allocate laptops to people to minimize total sadness. + """ + def calculate_sadness_score(person: Person, laptop: Laptop) -> int: + try: + return person.preferred_operating_systems.index(laptop.operating_system) + except ValueError: + return 100 + + allocated_laptops = {} + + # create a shallow copy of the laptops list + available_laptops = laptops[:] + + for person in people: + # ensure available_laptops is not empty before calling min + if not available_laptops: + raise ValueError("No laptops available to allocate.") + + # use min() to select the laptop with the lowest sadness score for the person. + # lambda function is scoring the person's preferences by calculating the "sadness score" for each laptop based on the index position + best_laptop = min(available_laptops, key=lambda laptop: calculate_sadness_score(person, laptop), default=None) + if best_laptop: + allocated_laptops[person.name] = best_laptop + available_laptops.remove(best_laptop) + + + if len(allocated_laptops) != len(people): + raise ValueError("Not enough laptops to allocate one to each person.") + + return allocated_laptops + + +def main(): + # Prompt the user for their details and add them to the people list + new_person = user_prompt() + people.append(new_person) + + # Allocate laptops and print the results + allocation = allocate_laptops(people, laptops_list) + for name, laptop in allocation.items(): + print(f"{name} was allocated {laptop.manufacturer} {laptop.model} with {laptop.operating_system.value}") + +# Ensure the script runs only when executed directly +if __name__ == "__main__": + main() \ No newline at end of file From 70c441b39415e332704805aabb0c2c490e6e7687 Mon Sep 17 00:00:00 2001 From: Geraldine-Edwards Date: Sat, 6 Dec 2025 09:51:00 +0000 Subject: [PATCH 4/7] Refactor laptop allocation logic to use tuple for preferred operating systems and extract sadness score calculation into a separate function --- .../laptop_allocaton.py | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/implement-laptop-allocation/laptop_allocaton.py b/implement-laptop-allocation/laptop_allocaton.py index 67e24a73b..1f394b656 100644 --- a/implement-laptop-allocation/laptop_allocaton.py +++ b/implement-laptop-allocation/laptop_allocaton.py @@ -1,7 +1,7 @@ import sys from dataclasses import dataclass from enum import Enum -from typing import List, Dict +from typing import List, Dict, Tuple class OperatingSystem(Enum): """enumeration of available operating systems.""" @@ -15,7 +15,7 @@ class Person: name: str age: int # listed in order of preference - preferred_operating_systems: List[OperatingSystem] + preferred_operating_systems: Tuple[OperatingSystem, ...] @dataclass(frozen=True) @@ -108,17 +108,20 @@ def find_possible_laptops(available_laptops: List[Laptop], current_person: Perso if laptop.operating_system in current_person.preferred_operating_systems ] +def sadness_score(person: Person, laptop: Laptop) -> int: + """ + calculate the sadness score for a person based on the allocated laptop. + """ + try: + return person.preferred_operating_systems.index(laptop.operating_system) + except ValueError: + return 100 + def allocate_laptops(people: List[Person], laptops: List[Laptop]) -> Dict[Person, Laptop]: """ allocate laptops to people to minimize total sadness. """ - def calculate_sadness_score(person: Person, laptop: Laptop) -> int: - try: - return person.preferred_operating_systems.index(laptop.operating_system) - except ValueError: - return 100 - allocated_laptops = {} # create a shallow copy of the laptops list @@ -129,9 +132,9 @@ def calculate_sadness_score(person: Person, laptop: Laptop) -> int: if not available_laptops: raise ValueError("No laptops available to allocate.") - # use min() to select the laptop with the lowest sadness score for the person. - # lambda function is scoring the person's preferences by calculating the "sadness score" for each laptop based on the index position - best_laptop = min(available_laptops, key=lambda laptop: calculate_sadness_score(person, laptop), default=None) + # use min() to find the laptop that minimizes their 'sadness score' + # lambda function is scoring the person's preferences by calculating the 'sadness score' for each laptop based on the index position + best_laptop = min(available_laptops, key=lambda laptop: sadness_score(person, laptop), default=None) if best_laptop: allocated_laptops[person.name] = best_laptop available_laptops.remove(best_laptop) @@ -151,7 +154,8 @@ def main(): # Allocate laptops and print the results allocation = allocate_laptops(people, laptops_list) for name, laptop in allocation.items(): - print(f"{name} was allocated {laptop.manufacturer} {laptop.model} with {laptop.operating_system.value}") + person_sadness_score = sadness_score(next(person for person in people if person.name == name), laptop) + print(f"{name} was allocated {laptop.manufacturer} {laptop.model} with {laptop.operating_system.value} (Score: {person_sadness_score})") # Ensure the script runs only when executed directly if __name__ == "__main__": From c6d2222058c70fa2a9f956d7953f16cf8fe90f3d Mon Sep 17 00:00:00 2001 From: Geraldine-Edwards Date: Sat, 6 Dec 2025 16:58:31 +0000 Subject: [PATCH 5/7] Update user prompt to include order of preference for operating systems and improve error handling in laptop allocation --- .../laptop_allocaton.py | 61 +++++++++---------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/implement-laptop-allocation/laptop_allocaton.py b/implement-laptop-allocation/laptop_allocaton.py index 1f394b656..79c61f907 100644 --- a/implement-laptop-allocation/laptop_allocaton.py +++ b/implement-laptop-allocation/laptop_allocaton.py @@ -55,12 +55,13 @@ class Laptop: Person(name="Ger", age=51, preferred_operating_systems=[OperatingSystem.UBUNTU, OperatingSystem.MACOS]), ] + +# updated user prompt to include order of preference in selecting OS def user_prompt() -> Person: """ prompt the user to input their details and preferred operating systems """ try: - # strip() whitespace before processing (no need for str type here as input always returns a string) name = input("Please enter your first name: ").strip() if not name.isalpha(): @@ -88,7 +89,7 @@ def user_prompt() -> Person: for os_name in preferred_os_list: if os_name not in valid_os: raise ValueError(f"Invalid operating system: {os_name}") - # convert to enum + # convert to enum preferred_os_enum.append(OperatingSystem(os_name)) return Person(name=name, age=age, preferred_operating_systems=tuple(preferred_os_enum)) @@ -96,48 +97,37 @@ def user_prompt() -> Person: # throw an error and exit for invalid age and os input except ValueError as error: print(f"Invalid input: {error}", file=sys.stderr) - sys.exit(1) -def find_possible_laptops(available_laptops: List[Laptop], current_person: Person) -> List[Laptop]: - """ - find laptops that match a person's preferred operating systems. - """ - return [ - laptop for laptop in available_laptops - if laptop.operating_system in current_person.preferred_operating_systems - ] - def sadness_score(person: Person, laptop: Laptop) -> int: """ calculate the sadness score for a person based on the allocated laptop. """ - try: + if laptop.operating_system in person.preferred_operating_systems: return person.preferred_operating_systems.index(laptop.operating_system) - except ValueError: - return 100 - + return 100 def allocate_laptops(people: List[Person], laptops: List[Laptop]) -> Dict[Person, Laptop]: """ allocate laptops to people to minimize total sadness. """ - allocated_laptops = {} + allocated_laptops : Dict[str, Laptop ]= {} # create a shallow copy of the laptops list available_laptops = laptops[:] - for person in people: + sorted_people = sorted(people, key=lambda p: len(p.preferred_operating_systems)) + + for person in sorted_people: # ensure available_laptops is not empty before calling min if not available_laptops: raise ValueError("No laptops available to allocate.") # use min() to find the laptop that minimizes their 'sadness score' # lambda function is scoring the person's preferences by calculating the 'sadness score' for each laptop based on the index position - best_laptop = min(available_laptops, key=lambda laptop: sadness_score(person, laptop), default=None) - if best_laptop: - allocated_laptops[person.name] = best_laptop - available_laptops.remove(best_laptop) + best_laptop = min(available_laptops, key=lambda laptop: sadness_score(person, laptop)) + allocated_laptops[person.name] = best_laptop + available_laptops.remove(best_laptop) if len(allocated_laptops) != len(people): @@ -147,15 +137,24 @@ def allocate_laptops(people: List[Person], laptops: List[Laptop]) -> Dict[Person def main(): - # Prompt the user for their details and add them to the people list - new_person = user_prompt() - people.append(new_person) - - # Allocate laptops and print the results - allocation = allocate_laptops(people, laptops_list) - for name, laptop in allocation.items(): - person_sadness_score = sadness_score(next(person for person in people if person.name == name), laptop) - print(f"{name} was allocated {laptop.manufacturer} {laptop.model} with {laptop.operating_system.value} (Score: {person_sadness_score})") + """ + allocate laptops and display results + """ + try: + # prompt the user for their details and add them to the people list + new_person = user_prompt() + people.append(new_person) + + # allocate laptops and print the results + allocation = allocate_laptops(people, laptops_list) + + for name, laptop in allocation.items(): + person = next(person for person in people if person.name == name) + person_sadness_score = sadness_score(person, laptop) + print(f"{name} was allocated {laptop.manufacturer} {laptop.model} with {laptop.operating_system.value} (Score: {person_sadness_score})") + + except Exception as error: + print(f"An error occurred: {error}", file=sys.stderr) # Ensure the script runs only when executed directly if __name__ == "__main__": From f8cec05fca74379f070386c6a27ecf5446ed5547 Mon Sep 17 00:00:00 2001 From: Geraldine-Edwards Date: Sat, 6 Dec 2025 17:07:00 +0000 Subject: [PATCH 6/7] Sort available laptops by ID and prioritize people with shorter preference lists in laptop allocation --- implement-laptop-allocation/laptop_allocaton.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/implement-laptop-allocation/laptop_allocaton.py b/implement-laptop-allocation/laptop_allocaton.py index 79c61f907..4264bfda5 100644 --- a/implement-laptop-allocation/laptop_allocaton.py +++ b/implement-laptop-allocation/laptop_allocaton.py @@ -113,9 +113,10 @@ def allocate_laptops(people: List[Person], laptops: List[Laptop]) -> Dict[Person """ allocated_laptops : Dict[str, Laptop ]= {} - # create a shallow copy of the laptops list - available_laptops = laptops[:] - + # create a shallow copy of the laptops list sorted by id + available_laptops = sorted(laptops, key=lambda l: l.id) + + # sort people by length of their preferences first (avoids a score of 100 if possible) sorted_people = sorted(people, key=lambda p: len(p.preferred_operating_systems)) for person in sorted_people: From 0ecb81bdac496061de5350f5a58dc90ead4153f8 Mon Sep 17 00:00:00 2001 From: Geraldine-Edwards Date: Fri, 2 Jan 2026 20:34:55 +0000 Subject: [PATCH 7/7] Enhance user input validation in name prompt to allow hyphens and spaces in names and embed minimum age variable into age validation for accuracy. --- implement-laptop-allocation/laptop_allocaton.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/implement-laptop-allocation/laptop_allocaton.py b/implement-laptop-allocation/laptop_allocaton.py index 4264bfda5..c562cb160 100644 --- a/implement-laptop-allocation/laptop_allocaton.py +++ b/implement-laptop-allocation/laptop_allocaton.py @@ -1,3 +1,4 @@ +import re import sys from dataclasses import dataclass from enum import Enum @@ -63,15 +64,17 @@ def user_prompt() -> Person: """ try: # strip() whitespace before processing (no need for str type here as input always returns a string) + # use 're' import (regular expression) for regex validation for allowed name characters name = input("Please enter your first name: ").strip() - if not name.isalpha(): - raise ValueError("Name must contain only alphabetic characters.") + if not re.fullmatch(r"[A-Za-z\- ]+", name): + raise ValueError("Name must contain only alphabetic characters, hyphens, or spaces.") + # strip() before converting to integer age = int(input("Please enter your age: ").strip()) minimum_age = 18 if age < minimum_age: - raise ValueError("Age must be 18 or over.") + raise ValueError("Age must be {minimum_age} or over.") # define valid OS options