From 01e59e9a36ebe0f5c79d26ee0ef3bdf9942fbe27 Mon Sep 17 00:00:00 2001 From: Sara Tahir Date: Sun, 7 Dec 2025 17:36:34 +0000 Subject: [PATCH 1/5] Initiating data sets for person and laptops class for laptop allocation. --- laptop Allocation/laptop.py | 63 +++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 laptop Allocation/laptop.py diff --git a/laptop Allocation/laptop.py b/laptop Allocation/laptop.py new file mode 100644 index 00000000..2cd69fb0 --- /dev/null +++ b/laptop Allocation/laptop.py @@ -0,0 +1,63 @@ +from dataclasses import dataclass +from enum import Enum +from typing import Dict, List +import sys + +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_systems: List[OperatingSystem] + + +@dataclass(frozen=True) +class Laptop: + id: int + manufacturer: str + model: str + screen_size_in_inches: float + operating_system: OperatingSystem + +#library of laptops +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", screen_size_in_inches=13, operating_system=OperatingSystem.MACOS), + Laptop(id=6, manufacturer="Dell", model="macBook", screen_size_in_inches=13, operating_system=OperatingSystem.ARCH), + Laptop(id=7, manufacturer="Apple", model="macBook", screen_size_in_inches=13, operating_system=OperatingSystem.MACOS), + Laptop(id=8, manufacturer="Apple", model="macBook", screen_size_in_inches=13, operating_system=OperatingSystem.MACOS), +] +# Preset dataset of people +people = [ + Person(name="Sara", age=31, preferred_operating_system=[OperatingSystem.ARCH,OperatingSystem.UBUNTU]), + Person(name="Shabs", age=40, preferred_operating_system=[OperatingSystem.ARCH,OperatingSystem.MACOS,OperatingSystem.UBUNTU]), + Person(name="Jawad", age= 36, preferred_operating_system=[OperatingSystem.MACOS,OperatingSystem.UBUNTU,OperatingSystem.ARCH]), + Person(name="Mike", age=35, preferred_operating_system=[OperatingSystem.MACOS,OperatingSystem.ARCH]), + Person(name="Mawra", age=28, preferred_operating_system=[OperatingSystem.MACOS]), + Person(name="Fatma", age= 22, preferred_operating_system=[OperatingSystem.UBUNTU,OperatingSystem.ARCH]), + Person(name="Muhib", age= 19, preferred_operating_system=[OperatingSystem.MACOS,OperatingSystem.UBUNTU]), + +] + +def sadness(person: Person, laptop: Laptop) -> int: + if laptop.operating_system == person.preferred_operating_systems[0]: + return 0 + elif len(person.preferred_operating_systems) > 1 and laptop.operating_system == person.preferred_operating_systems[1]: + return 1 + elif len(person.preferred_operating_systems) > 2 and laptop.operating_system == person.preferred_operating_systems[2]: + return 2 + else: + return 100 + +#There are two approaches to solve this +#Greedy approach or Hungarian Algorithm Approach + +#def allocate_laptops(people: List[Person], laptops: List[Laptop]) -> Dict[Person, Laptop]: From 04009aaa5175a36335a1399b6d67eff95022159e Mon Sep 17 00:00:00 2001 From: Sara Tahir Date: Sun, 7 Dec 2025 19:42:13 +0000 Subject: [PATCH 2/5] Using Hungarian Algorithm for laptop allocation to minimize sadness. --- laptop Allocation/.vscode/settings.json | 3 ++ laptop Allocation/laptop.py | 44 +++++++++++++++++++------ 2 files changed, 37 insertions(+), 10 deletions(-) create mode 100644 laptop Allocation/.vscode/settings.json diff --git a/laptop Allocation/.vscode/settings.json b/laptop Allocation/.vscode/settings.json new file mode 100644 index 00000000..7e68766a --- /dev/null +++ b/laptop Allocation/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python-envs.pythonProjects": [] +} \ No newline at end of file diff --git a/laptop Allocation/laptop.py b/laptop Allocation/laptop.py index 2cd69fb0..ac6bc43e 100644 --- a/laptop Allocation/laptop.py +++ b/laptop Allocation/laptop.py @@ -2,6 +2,8 @@ from enum import Enum from typing import Dict, List import sys +import numpy as np #to build and mnaiplate our sadness grid +from scipy.optimize import linear_sum_assignment #to find optimal laptop allocaation to minimize sadness across all users class OperatingSystem(Enum): MACOS = "macOS" @@ -37,13 +39,13 @@ class Laptop: ] # Preset dataset of people people = [ - Person(name="Sara", age=31, preferred_operating_system=[OperatingSystem.ARCH,OperatingSystem.UBUNTU]), - Person(name="Shabs", age=40, preferred_operating_system=[OperatingSystem.ARCH,OperatingSystem.MACOS,OperatingSystem.UBUNTU]), - Person(name="Jawad", age= 36, preferred_operating_system=[OperatingSystem.MACOS,OperatingSystem.UBUNTU,OperatingSystem.ARCH]), - Person(name="Mike", age=35, preferred_operating_system=[OperatingSystem.MACOS,OperatingSystem.ARCH]), - Person(name="Mawra", age=28, preferred_operating_system=[OperatingSystem.MACOS]), - Person(name="Fatma", age= 22, preferred_operating_system=[OperatingSystem.UBUNTU,OperatingSystem.ARCH]), - Person(name="Muhib", age= 19, preferred_operating_system=[OperatingSystem.MACOS,OperatingSystem.UBUNTU]), + Person(name="Sara", age=31, preferred_operating_systems=[OperatingSystem.ARCH,OperatingSystem.UBUNTU]), + Person(name="Shabs", age=40, preferred_operating_systems=[OperatingSystem.ARCH,OperatingSystem.MACOS,OperatingSystem.UBUNTU]), + Person(name="Jawad", age= 36, preferred_operating_systems=[OperatingSystem.MACOS,OperatingSystem.UBUNTU,OperatingSystem.ARCH]), + Person(name="Mike", age=35, preferred_operating_systems=[OperatingSystem.MACOS,OperatingSystem.ARCH]), + Person(name="Mawra", age=28, preferred_operating_systems=[OperatingSystem.MACOS]), + Person(name="Fatma", age= 22, preferred_operating_systems=[OperatingSystem.UBUNTU,OperatingSystem.ARCH]), + Person(name="Muhib", age= 19, preferred_operating_systems=[OperatingSystem.MACOS,OperatingSystem.UBUNTU]), ] @@ -57,7 +59,29 @@ def sadness(person: Person, laptop: Laptop) -> int: else: return 100 -#There are two approaches to solve this -#Greedy approach or Hungarian Algorithm Approach +#I have chose to use Hungarian Algorithm Approach to solve the problem. -#def allocate_laptops(people: List[Person], laptops: List[Laptop]) -> Dict[Person, Laptop]: +def allocate_laptops(people: List[Person], laptops: List[Laptop]) -> Dict[Person, Laptop]: + n_people = len(people) #length of people [] + n_laptops = len(laptops) #length of laptops [] + if n_laptops < n_people: + raise ValueError("Not enough laptops to allocate one to each person.") + sadness_matrix = np.zeros((n_people, n_laptops), dtype= int) #npzero mean each cell in 2d grid is a zero and dtype is datatype int so it int 0 + for i, person in enumerate(people): #enumerate gives us both index position and the item itself from the list + for j, laptop in enumerate(laptops): + sadness_matrix[i,j] = sadness(person, laptop) #this matrix represents complete laptop allocation + # Hungarian algorithm to run on our sadness matrix + row_indices, col_indices = linear_sum_assignment(sadness_matrix) #which person which laptop + # Map people to laptops + allocation = {} + for i, j in zip(row_indices, col_indices):#it pairs up results (person indices and laptop indices). The loop then uses those pairs to build the final allocation dictionary + allocation[people[i].name] = laptops[j] + return allocation + + +def print_allocation(allocation: Dict[Person, Laptop]): + for name, lap in allocation.items(): + print(f"{name} gets Laptop {lap.id} ({lap.operating_system.value})") + +allocation = allocate_laptops(people, laptop) +print_allocation(allocation) \ No newline at end of file From 3d1c0c8a199f1fce1ba25cb87524640c734ad31b Mon Sep 17 00:00:00 2001 From: Sara Tahir Date: Mon, 19 Jan 2026 21:32:18 +0000 Subject: [PATCH 3/5] Adding changes to improve my code based on review --- .../.vscode/settings.json | 0 .../laptop.py | 63 +++++++++++-------- laptopAllocation/tests/test_laptop.py | 12 ++++ 3 files changed, 50 insertions(+), 25 deletions(-) rename {laptop Allocation => laptopAllocation}/.vscode/settings.json (100%) rename {laptop Allocation => laptopAllocation}/laptop.py (59%) create mode 100644 laptopAllocation/tests/test_laptop.py diff --git a/laptop Allocation/.vscode/settings.json b/laptopAllocation/.vscode/settings.json similarity index 100% rename from laptop Allocation/.vscode/settings.json rename to laptopAllocation/.vscode/settings.json diff --git a/laptop Allocation/laptop.py b/laptopAllocation/laptop.py similarity index 59% rename from laptop Allocation/laptop.py rename to laptopAllocation/laptop.py index ac6bc43e..aa676748 100644 --- a/laptop Allocation/laptop.py +++ b/laptopAllocation/laptop.py @@ -1,7 +1,6 @@ from dataclasses import dataclass from enum import Enum from typing import Dict, List -import sys import numpy as np #to build and mnaiplate our sadness grid from scipy.optimize import linear_sum_assignment #to find optimal laptop allocaation to minimize sadness across all users @@ -15,7 +14,7 @@ class Person: name: str age: int # Sorted in order of preference, most preferred is first. - preferred_operating_systems: List[OperatingSystem] + preferred_operating_systems: tuple[OperatingSystem] @dataclass(frozen=True) @@ -27,7 +26,7 @@ class Laptop: operating_system: OperatingSystem #library of laptops -laptop = [ +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), @@ -39,28 +38,42 @@ class Laptop: ] # Preset dataset of people people = [ - Person(name="Sara", age=31, preferred_operating_systems=[OperatingSystem.ARCH,OperatingSystem.UBUNTU]), - Person(name="Shabs", age=40, preferred_operating_systems=[OperatingSystem.ARCH,OperatingSystem.MACOS,OperatingSystem.UBUNTU]), - Person(name="Jawad", age= 36, preferred_operating_systems=[OperatingSystem.MACOS,OperatingSystem.UBUNTU,OperatingSystem.ARCH]), - Person(name="Mike", age=35, preferred_operating_systems=[OperatingSystem.MACOS,OperatingSystem.ARCH]), - Person(name="Mawra", age=28, preferred_operating_systems=[OperatingSystem.MACOS]), - Person(name="Fatma", age= 22, preferred_operating_systems=[OperatingSystem.UBUNTU,OperatingSystem.ARCH]), - Person(name="Muhib", age= 19, preferred_operating_systems=[OperatingSystem.MACOS,OperatingSystem.UBUNTU]), + Person(name="Sara", age=31, preferred_operating_systems=(OperatingSystem.ARCH,OperatingSystem.UBUNTU)), + Person(name="Shabs", age=40, preferred_operating_systems=(OperatingSystem.ARCH,OperatingSystem.MACOS,OperatingSystem.UBUNTU)), + Person(name="Jawad", age= 36, preferred_operating_systems=(OperatingSystem.MACOS,OperatingSystem.UBUNTU,OperatingSystem.ARCH)), + Person(name="Mike", age=35, preferred_operating_systems=(OperatingSystem.MACOS,OperatingSystem.ARCH)), + Person(name="Mawra", age=28, preferred_operating_systems=(OperatingSystem.MACOS,)),#adding a comma for one item tuple + Person(name="Fatma", age= 22, preferred_operating_systems=(OperatingSystem.UBUNTU,OperatingSystem.ARCH)), + Person(name="Muhib", age= 19, preferred_operating_systems=(OperatingSystem.MACOS,OperatingSystem.UBUNTU)), ] + +# Updated based on feedback: +# Instead of hard‑coding sadness scores (0, 1, 2), this version uses the +# position of the laptop’s OS in the person’s preference list to decide the sadness value. +# I check for membership first (so no exceptions), then +# use the index as the rank. Only the top MAX_HAPPY_RANK preferences count; +# anything lower or not listed gets a high penalty. + +MAX_HAPPY_RANK = 3 +TERRIBLE_SADNESS = 100 def sadness(person: Person, laptop: Laptop) -> int: - if laptop.operating_system == person.preferred_operating_systems[0]: - return 0 - elif len(person.preferred_operating_systems) > 1 and laptop.operating_system == person.preferred_operating_systems[1]: - return 1 - elif len(person.preferred_operating_systems) > 2 and laptop.operating_system == person.preferred_operating_systems[2]: - return 2 - else: - return 100 - -#I have chose to use Hungarian Algorithm Approach to solve the problem. + prefs = person.preferred_operating_systems + + if laptop.operating_system not in prefs: + return TERRIBLE_SADNESS + rank = prefs.index(laptop.operating_system) + + return rank if rank < MAX_HAPPY_RANK else TERRIBLE_SADNESS + + #Allocate laptops to people in a way that minimises total sadness. + #Builds a cost matrix using the sadness() function and applies the + #Hungarian algorithm (linear_sum_assignment) to find the optimal + #one‑to‑one assignment between people and laptops. + + def allocate_laptops(people: List[Person], laptops: List[Laptop]) -> Dict[Person, Laptop]: n_people = len(people) #length of people [] n_laptops = len(laptops) #length of laptops [] @@ -73,15 +86,15 @@ def allocate_laptops(people: List[Person], laptops: List[Laptop]) -> Dict[Person # Hungarian algorithm to run on our sadness matrix row_indices, col_indices = linear_sum_assignment(sadness_matrix) #which person which laptop # Map people to laptops - allocation = {} + allocation: Dict[Person, Laptop] = {} for i, j in zip(row_indices, col_indices):#it pairs up results (person indices and laptop indices). The loop then uses those pairs to build the final allocation dictionary - allocation[people[i].name] = laptops[j] + allocation[people[i]] = laptops[j] return allocation def print_allocation(allocation: Dict[Person, Laptop]): - for name, lap in allocation.items(): - print(f"{name} gets Laptop {lap.id} ({lap.operating_system.value})") + for person, lap in allocation.items(): + print(f"{person.name} gets Laptop {lap.id} ({lap.operating_system.value})") -allocation = allocate_laptops(people, laptop) +allocation = allocate_laptops(people, laptops) print_allocation(allocation) \ No newline at end of file diff --git a/laptopAllocation/tests/test_laptop.py b/laptopAllocation/tests/test_laptop.py new file mode 100644 index 00000000..bb53e068 --- /dev/null +++ b/laptopAllocation/tests/test_laptop.py @@ -0,0 +1,12 @@ +# test_basic.py +# A very small test just to confirm the core logic works. +# Full testing is beyond the scope of this assignment, but this shows +# that the sadness function behaves correctly for a simple case. +#If a person gets a laptop with their first‑choice operating system, their sadness should be 0. +from laptop import Person, Laptop, OperatingSystem, sadness + +def test_sadness_first_choice(): + person = Person("Test", 20, (OperatingSystem.MACOS,)) + laptop = Laptop(1, "Apple", "macBook", 13, OperatingSystem.MACOS) + + assert sadness(person, laptop) == 0 From f82feda79348c4ae5d1610e0b7b0ece96584e806 Mon Sep 17 00:00:00 2001 From: Sara Tahir Date: Tue, 20 Jan 2026 13:32:48 +0000 Subject: [PATCH 4/5] Adding more tests --- laptopAllocation/tests/test_laptop.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/laptopAllocation/tests/test_laptop.py b/laptopAllocation/tests/test_laptop.py index bb53e068..f93c8c63 100644 --- a/laptopAllocation/tests/test_laptop.py +++ b/laptopAllocation/tests/test_laptop.py @@ -1,7 +1,4 @@ -# test_basic.py -# A very small test just to confirm the core logic works. -# Full testing is beyond the scope of this assignment, but this shows -# that the sadness function behaves correctly for a simple case. + #If a person gets a laptop with their first‑choice operating system, their sadness should be 0. from laptop import Person, Laptop, OperatingSystem, sadness @@ -10,3 +7,25 @@ def test_sadness_first_choice(): laptop = Laptop(1, "Apple", "macBook", 13, OperatingSystem.MACOS) assert sadness(person, laptop) == 0 +from laptop import Person, Laptop, OperatingSystem, sadness, TERRIBLE_SADNESS +#Adding more tests +def test_sadness_second_choice(): + person = Person("Test", 20, (OperatingSystem.MACOS, OperatingSystem.UBUNTU)) + laptop = Laptop(1, "Dell", "XPS", 13, OperatingSystem.UBUNTU) + assert sadness(person, laptop) == 1 + + +def test_sadness_not_in_preferences(): + person = Person("Test", 20, (OperatingSystem.MACOS,)) + laptop = Laptop(1, "Dell", "XPS", 13, OperatingSystem.UBUNTU) + assert sadness(person, laptop) == TERRIBLE_SADNESS + + +def test_sadness_above_max_rank(): + # as our MAX_HAPPY_RANK = 3, so rank 3 should be TERRIBLE_SADNESS + person = Person( + "Test", 20, + (OperatingSystem.MACOS, OperatingSystem.UBUNTU, OperatingSystem.ARCH) + ) + laptop = Laptop(1, "Dell", "XPS", 13, OperatingSystem.ARCH) + assert sadness(person, laptop) == TERRIBLE_SADNESS From 1620fd605f250a9c114937419256777b947d741f Mon Sep 17 00:00:00 2001 From: Sara Tahir Date: Tue, 20 Jan 2026 13:33:06 +0000 Subject: [PATCH 5/5] Add pytest.ini to ensure project root is on Python path for tests --- laptopAllocation/pytest.ini | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 laptopAllocation/pytest.ini diff --git a/laptopAllocation/pytest.ini b/laptopAllocation/pytest.ini new file mode 100644 index 00000000..03f586d4 --- /dev/null +++ b/laptopAllocation/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +pythonpath = . \ No newline at end of file