From 2567a98de9c03cce137e9290f6e8f663b7149298 Mon Sep 17 00:00:00 2001 From: RahwaZeslusHaile Date: Sat, 29 Nov 2025 21:16:03 +0000 Subject: [PATCH 1/4] Ignore local virtual environment --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 3c3629e6..274d0491 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules +.venv \ No newline at end of file From 620f093767835a6674f3b049ce4a586b18b88dfd Mon Sep 17 00:00:00 2001 From: RahwaZeslusHaile Date: Mon, 8 Dec 2025 15:14:47 +0000 Subject: [PATCH 2/4] Add laptop allocation with sadness minimization and detailed output --- sprint-5/laptop-allocation.py | 88 +++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 sprint-5/laptop-allocation.py diff --git a/sprint-5/laptop-allocation.py b/sprint-5/laptop-allocation.py new file mode 100644 index 00000000..a4139862 --- /dev/null +++ b/sprint-5/laptop-allocation.py @@ -0,0 +1,88 @@ +from dataclasses import dataclass +from enum import Enum +from typing import List, Dict +from itertools import permutations + +unpreferred_OS_penalty = 100 + +class OperatingSystem(Enum): + MACOS = "macOS" + ARCH = "Arch Linux" + UBUNTU = "Ubuntu" + +@dataclass(frozen=True) +class Person: + name: str + age: int + preferred_operating_system: tuple + +@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]: + if len(people) != len(laptops): + raise ValueError("Number of people must match number of laptops") + + best_assignment = None + lowest_sadness = float("inf") + + for perm in permutations(laptops): + total_sadness = 0 + for person, laptop in zip(people, perm): + if laptop.operating_system in person.preferred_operating_system: + sadness = person.preferred_operating_system.index(laptop.operating_system) + else: + sadness = unpreferred_OS_penalty + total_sadness += sadness + + if total_sadness < lowest_sadness: + lowest_sadness = total_sadness + best_assignment = perm + + return {person: laptop for person, laptop in zip(people, best_assignment)} + +laptops = [ + Laptop(1, "Dell", "XPS 13", 13, OperatingSystem.ARCH), + Laptop(2, "HP", "Spectre 15", 15, OperatingSystem.UBUNTU), + Laptop(3, "Lenovo", "ThinkPad 14", 14, OperatingSystem.UBUNTU), + Laptop(4, "Apple", "MacBook Air", 13, OperatingSystem.MACOS), + Laptop(5, "Apple", "MacBook Pro", 16, OperatingSystem.MACOS), + Laptop(6, "Dell", "Latitude", 15, OperatingSystem.ARCH), + Laptop(7, "HP", "EliteBook", 13, OperatingSystem.MACOS), + Laptop(8, "Lenovo", "Yoga", 14, OperatingSystem.UBUNTU) +] + +people = [ + Person("Alice", 29, (OperatingSystem.UBUNTU, OperatingSystem.MACOS)), + Person("Bob", 34, (OperatingSystem.ARCH, OperatingSystem.UBUNTU)), + Person("Charlie", 40, (OperatingSystem.MACOS, OperatingSystem.ARCH)), + Person("Diana", 25, (OperatingSystem.MACOS,)), + Person("Ethan", 31, (OperatingSystem.UBUNTU, OperatingSystem.ARCH)), + Person("Fiona", 27, (OperatingSystem.MACOS, OperatingSystem.UBUNTU)), + Person("George", 22, (OperatingSystem.ARCH, OperatingSystem.MACOS)), + Person("Zara", 33, (OperatingSystem.ARCH, OperatingSystem.MACOS)) +] + +assignment = allocate_laptops(people, laptops) + + +for person, laptop in assignment.items(): + person_sadness_score = ( + person.preferred_operating_system.index(laptop.operating_system) + if laptop.operating_system in person.preferred_operating_system + else unpreferred_OS_penalty + ) + print(f"{person.name} was allocated {laptop.manufacturer} {laptop.model} " + f"with {laptop.operating_system.value} (Score: {person_sadness_score})") + +total_sadness = sum( + person.preferred_operating_system.index(laptop.operating_system) + if laptop.operating_system in person.preferred_operating_system else unpreferred_OS_penalty + for person, laptop in assignment.items() +) +print("\nTotal sadness:", total_sadness) From 2b38492521ccbc59f5de963cf004853e06d7304e Mon Sep 17 00:00:00 2001 From: RahwaZeslusHaile Date: Fri, 16 Jan 2026 20:36:19 +0000 Subject: [PATCH 3/4] Refactor laptop allocation: optimize algorithm for scalability, fix HP/MacOS data, add requirements.txt, and implement full unit tests --- .gitignore | 4 +- ...top-allocation.py => laptop_allocation.py} | 41 ++++++------- sprint-5/requirements.txt | 2 + sprint-5/test_laptop_allocation.py | 61 +++++++++++++++++++ 4 files changed, 86 insertions(+), 22 deletions(-) rename sprint-5/{laptop-allocation.py => laptop_allocation.py} (72%) create mode 100644 sprint-5/requirements.txt create mode 100644 sprint-5/test_laptop_allocation.py diff --git a/.gitignore b/.gitignore index 274d0491..f006d252 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ node_modules -.venv \ No newline at end of file +.venv +__pycache__ +*.pyc diff --git a/sprint-5/laptop-allocation.py b/sprint-5/laptop_allocation.py similarity index 72% rename from sprint-5/laptop-allocation.py rename to sprint-5/laptop_allocation.py index a4139862..84d51a67 100644 --- a/sprint-5/laptop-allocation.py +++ b/sprint-5/laptop_allocation.py @@ -1,7 +1,8 @@ from dataclasses import dataclass from enum import Enum from typing import List, Dict -from itertools import permutations +import numpy as np +from scipy.optimize import linear_sum_assignment unpreferred_OS_penalty = 100 @@ -14,7 +15,7 @@ class OperatingSystem(Enum): class Person: name: str age: int - preferred_operating_system: tuple + preferred_operating_system: tuple @dataclass(frozen=True) class Laptop: @@ -27,25 +28,22 @@ class Laptop: def allocate_laptops(people: List[Person], laptops: List[Laptop]) -> Dict[Person, Laptop]: if len(people) != len(laptops): raise ValueError("Number of people must match number of laptops") + + n = len(people) + cost_matrix = np.zeros((n, n), dtype=int) - best_assignment = None - lowest_sadness = float("inf") - - for perm in permutations(laptops): - total_sadness = 0 - for person, laptop in zip(people, perm): + for i, person in enumerate(people): + for j, laptop in enumerate(laptops): if laptop.operating_system in person.preferred_operating_system: - sadness = person.preferred_operating_system.index(laptop.operating_system) + cost_matrix[i, j] = person.preferred_operating_system.index(laptop.operating_system) else: - sadness = unpreferred_OS_penalty - total_sadness += sadness - - if total_sadness < lowest_sadness: - lowest_sadness = total_sadness - best_assignment = perm + cost_matrix[i, j] = unpreferred_OS_penalty - return {person: laptop for person, laptop in zip(people, best_assignment)} + person_indices, laptop_indices = linear_sum_assignment(cost_matrix) + return { + people[i]: laptops[j] for i, j in zip(person_indices, laptop_indices) + } laptops = [ Laptop(1, "Dell", "XPS 13", 13, OperatingSystem.ARCH), Laptop(2, "HP", "Spectre 15", 15, OperatingSystem.UBUNTU), @@ -53,7 +51,7 @@ def allocate_laptops(people: List[Person], laptops: List[Laptop]) -> Dict[Person Laptop(4, "Apple", "MacBook Air", 13, OperatingSystem.MACOS), Laptop(5, "Apple", "MacBook Pro", 16, OperatingSystem.MACOS), Laptop(6, "Dell", "Latitude", 15, OperatingSystem.ARCH), - Laptop(7, "HP", "EliteBook", 13, OperatingSystem.MACOS), + Laptop(7, "HP", "EliteBook", 13, OperatingSystem.MACOS), # Reviewer joke: HP running macOS 😄 Laptop(8, "Lenovo", "Yoga", 14, OperatingSystem.UBUNTU) ] @@ -70,19 +68,20 @@ def allocate_laptops(people: List[Person], laptops: List[Laptop]) -> Dict[Person assignment = allocate_laptops(people, laptops) - +print("Laptop Assignments:\n") for person, laptop in assignment.items(): - person_sadness_score = ( + sadness = ( person.preferred_operating_system.index(laptop.operating_system) if laptop.operating_system in person.preferred_operating_system else unpreferred_OS_penalty ) - print(f"{person.name} was allocated {laptop.manufacturer} {laptop.model} " - f"with {laptop.operating_system.value} (Score: {person_sadness_score})") + print(f"{person.name} → {laptop.manufacturer} {laptop.model} " + f"({laptop.operating_system.value}) | Score: {sadness}") total_sadness = sum( person.preferred_operating_system.index(laptop.operating_system) if laptop.operating_system in person.preferred_operating_system else unpreferred_OS_penalty for person, laptop in assignment.items() ) + print("\nTotal sadness:", total_sadness) diff --git a/sprint-5/requirements.txt b/sprint-5/requirements.txt new file mode 100644 index 00000000..6bad1038 --- /dev/null +++ b/sprint-5/requirements.txt @@ -0,0 +1,2 @@ +numpy +scipy diff --git a/sprint-5/test_laptop_allocation.py b/sprint-5/test_laptop_allocation.py new file mode 100644 index 00000000..b88ab228 --- /dev/null +++ b/sprint-5/test_laptop_allocation.py @@ -0,0 +1,61 @@ +import unittest +from laptop_allocation import allocate_laptops, Person, Laptop, OperatingSystem, unpreferred_OS_penalty + +laptops = [ + Laptop(1, "Dell", "XPS 13", 13, OperatingSystem.ARCH), + Laptop(2, "HP", "Spectre 15", 15, OperatingSystem.UBUNTU), + Laptop(3, "Lenovo", "ThinkPad 14", 14, OperatingSystem.UBUNTU), + Laptop(4, "Apple", "MacBook Air", 13, OperatingSystem.MACOS), + Laptop(5, "Apple", "MacBook Pro", 16, OperatingSystem.MACOS), + Laptop(6, "Dell", "Latitude", 15, OperatingSystem.ARCH), + Laptop(7, "HP", "EliteBook", 13, OperatingSystem.MACOS), + Laptop(8, "Lenovo", "Yoga", 14, OperatingSystem.UBUNTU) +] + +people = [ + Person("Alice", 29, (OperatingSystem.UBUNTU, OperatingSystem.MACOS)), + Person("Bob", 34, (OperatingSystem.ARCH, OperatingSystem.UBUNTU)), + Person("Charlie", 40, (OperatingSystem.MACOS, OperatingSystem.ARCH)), + Person("Diana", 25, (OperatingSystem.MACOS,)), + Person("Ethan", 31, (OperatingSystem.UBUNTU, OperatingSystem.ARCH)), + Person("Fiona", 27, (OperatingSystem.MACOS, OperatingSystem.UBUNTU)), + Person("George", 22, (OperatingSystem.ARCH, OperatingSystem.MACOS)), + Person("Zara", 33, (OperatingSystem.ARCH, OperatingSystem.MACOS)) +] + +class TestLaptopAllocation(unittest.TestCase): + + def setUp(self): + self.assignment = allocate_laptops(people, laptops) + + def test_everyone_has_laptop(self): + assigned_people = set(self.assignment.keys()) + self.assertEqual(assigned_people, set(people), "Some people are missing a laptop") + + def test_no_duplicate_laptops(self): + assigned_laptop_ids = [laptop.id for laptop in self.assignment.values()] + self.assertEqual(len(assigned_laptop_ids), len(set(assigned_laptop_ids)), "Duplicate laptops assigned") + + def test_sadness_scores(self): + for person, laptop in self.assignment.items(): + if laptop.operating_system in person.preferred_operating_system: + score = person.preferred_operating_system.index(laptop.operating_system) + self.assertGreaterEqual(score, 0, f"Negative score for {person.name}") + else: + self.assertEqual(unpreferred_OS_penalty, 100, f"Unexpected unpreferred penalty for {person.name}") + + def test_total_sadness(self): + total_sadness = sum( + person.preferred_operating_system.index(laptop.operating_system) + if laptop.operating_system in person.preferred_operating_system else unpreferred_OS_penalty + for person, laptop in self.assignment.items() + ) + calculated_sadness = sum( + person.preferred_operating_system.index(laptop.operating_system) + if laptop.operating_system in person.preferred_operating_system else unpreferred_OS_penalty + for person, laptop in self.assignment.items() + ) + self.assertEqual(total_sadness, calculated_sadness, "Total sadness mismatch") + +if __name__ == "__main__": + unittest.main() From 6cdc16ca835b6898c91b534d54024065f1dfd832 Mon Sep 17 00:00:00 2001 From: RahwaZeslusHaile <78068179+RahwaZeslusHaile@users.noreply.github.com> Date: Tue, 20 Jan 2026 12:43:16 +0000 Subject: [PATCH 4/4] Fix formatting and clean up laptop allocation code --- sprint-5/laptop_allocation.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sprint-5/laptop_allocation.py b/sprint-5/laptop_allocation.py index 84d51a67..38107d15 100644 --- a/sprint-5/laptop_allocation.py +++ b/sprint-5/laptop_allocation.py @@ -44,6 +44,7 @@ def allocate_laptops(people: List[Person], laptops: List[Laptop]) -> Dict[Person return { people[i]: laptops[j] for i, j in zip(person_indices, laptop_indices) } + laptops = [ Laptop(1, "Dell", "XPS 13", 13, OperatingSystem.ARCH), Laptop(2, "HP", "Spectre 15", 15, OperatingSystem.UBUNTU), @@ -51,7 +52,7 @@ def allocate_laptops(people: List[Person], laptops: List[Laptop]) -> Dict[Person Laptop(4, "Apple", "MacBook Air", 13, OperatingSystem.MACOS), Laptop(5, "Apple", "MacBook Pro", 16, OperatingSystem.MACOS), Laptop(6, "Dell", "Latitude", 15, OperatingSystem.ARCH), - Laptop(7, "HP", "EliteBook", 13, OperatingSystem.MACOS), # Reviewer joke: HP running macOS 😄 + Laptop(7, "HP", "EliteBook", 13, OperatingSystem.MACOS), Laptop(8, "Lenovo", "Yoga", 14, OperatingSystem.UBUNTU) ] @@ -69,6 +70,7 @@ def allocate_laptops(people: List[Person], laptops: List[Laptop]) -> Dict[Person assignment = allocate_laptops(people, laptops) print("Laptop Assignments:\n") + for person, laptop in assignment.items(): sadness = ( person.preferred_operating_system.index(laptop.operating_system)