Skip to content

Commit 2b38492

Browse files
Refactor laptop allocation: optimize algorithm for scalability, fix HP/MacOS data, add requirements.txt, and implement full unit tests
1 parent 620f093 commit 2b38492

4 files changed

Lines changed: 86 additions & 22 deletions

File tree

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
node_modules
2-
.venv
2+
.venv
3+
__pycache__
4+
*.pyc
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from dataclasses import dataclass
22
from enum import Enum
33
from typing import List, Dict
4-
from itertools import permutations
4+
import numpy as np
5+
from scipy.optimize import linear_sum_assignment
56

67
unpreferred_OS_penalty = 100
78

@@ -14,7 +15,7 @@ class OperatingSystem(Enum):
1415
class Person:
1516
name: str
1617
age: int
17-
preferred_operating_system: tuple
18+
preferred_operating_system: tuple
1819

1920
@dataclass(frozen=True)
2021
class Laptop:
@@ -27,33 +28,30 @@ class Laptop:
2728
def allocate_laptops(people: List[Person], laptops: List[Laptop]) -> Dict[Person, Laptop]:
2829
if len(people) != len(laptops):
2930
raise ValueError("Number of people must match number of laptops")
31+
32+
n = len(people)
33+
cost_matrix = np.zeros((n, n), dtype=int)
3034

31-
best_assignment = None
32-
lowest_sadness = float("inf")
33-
34-
for perm in permutations(laptops):
35-
total_sadness = 0
36-
for person, laptop in zip(people, perm):
35+
for i, person in enumerate(people):
36+
for j, laptop in enumerate(laptops):
3737
if laptop.operating_system in person.preferred_operating_system:
38-
sadness = person.preferred_operating_system.index(laptop.operating_system)
38+
cost_matrix[i, j] = person.preferred_operating_system.index(laptop.operating_system)
3939
else:
40-
sadness = unpreferred_OS_penalty
41-
total_sadness += sadness
42-
43-
if total_sadness < lowest_sadness:
44-
lowest_sadness = total_sadness
45-
best_assignment = perm
40+
cost_matrix[i, j] = unpreferred_OS_penalty
4641

47-
return {person: laptop for person, laptop in zip(people, best_assignment)}
42+
person_indices, laptop_indices = linear_sum_assignment(cost_matrix)
4843

44+
return {
45+
people[i]: laptops[j] for i, j in zip(person_indices, laptop_indices)
46+
}
4947
laptops = [
5048
Laptop(1, "Dell", "XPS 13", 13, OperatingSystem.ARCH),
5149
Laptop(2, "HP", "Spectre 15", 15, OperatingSystem.UBUNTU),
5250
Laptop(3, "Lenovo", "ThinkPad 14", 14, OperatingSystem.UBUNTU),
5351
Laptop(4, "Apple", "MacBook Air", 13, OperatingSystem.MACOS),
5452
Laptop(5, "Apple", "MacBook Pro", 16, OperatingSystem.MACOS),
5553
Laptop(6, "Dell", "Latitude", 15, OperatingSystem.ARCH),
56-
Laptop(7, "HP", "EliteBook", 13, OperatingSystem.MACOS),
54+
Laptop(7, "HP", "EliteBook", 13, OperatingSystem.MACOS), # Reviewer joke: HP running macOS 😄
5755
Laptop(8, "Lenovo", "Yoga", 14, OperatingSystem.UBUNTU)
5856
]
5957

@@ -70,19 +68,20 @@ def allocate_laptops(people: List[Person], laptops: List[Laptop]) -> Dict[Person
7068

7169
assignment = allocate_laptops(people, laptops)
7270

73-
71+
print("Laptop Assignments:\n")
7472
for person, laptop in assignment.items():
75-
person_sadness_score = (
73+
sadness = (
7674
person.preferred_operating_system.index(laptop.operating_system)
7775
if laptop.operating_system in person.preferred_operating_system
7876
else unpreferred_OS_penalty
7977
)
80-
print(f"{person.name} was allocated {laptop.manufacturer} {laptop.model} "
81-
f"with {laptop.operating_system.value} (Score: {person_sadness_score})")
78+
print(f"{person.name} {laptop.manufacturer} {laptop.model} "
79+
f"({laptop.operating_system.value}) | Score: {sadness}")
8280

8381
total_sadness = sum(
8482
person.preferred_operating_system.index(laptop.operating_system)
8583
if laptop.operating_system in person.preferred_operating_system else unpreferred_OS_penalty
8684
for person, laptop in assignment.items()
8785
)
86+
8887
print("\nTotal sadness:", total_sadness)

sprint-5/requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
numpy
2+
scipy

sprint-5/test_laptop_allocation.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import unittest
2+
from laptop_allocation import allocate_laptops, Person, Laptop, OperatingSystem, unpreferred_OS_penalty
3+
4+
laptops = [
5+
Laptop(1, "Dell", "XPS 13", 13, OperatingSystem.ARCH),
6+
Laptop(2, "HP", "Spectre 15", 15, OperatingSystem.UBUNTU),
7+
Laptop(3, "Lenovo", "ThinkPad 14", 14, OperatingSystem.UBUNTU),
8+
Laptop(4, "Apple", "MacBook Air", 13, OperatingSystem.MACOS),
9+
Laptop(5, "Apple", "MacBook Pro", 16, OperatingSystem.MACOS),
10+
Laptop(6, "Dell", "Latitude", 15, OperatingSystem.ARCH),
11+
Laptop(7, "HP", "EliteBook", 13, OperatingSystem.MACOS),
12+
Laptop(8, "Lenovo", "Yoga", 14, OperatingSystem.UBUNTU)
13+
]
14+
15+
people = [
16+
Person("Alice", 29, (OperatingSystem.UBUNTU, OperatingSystem.MACOS)),
17+
Person("Bob", 34, (OperatingSystem.ARCH, OperatingSystem.UBUNTU)),
18+
Person("Charlie", 40, (OperatingSystem.MACOS, OperatingSystem.ARCH)),
19+
Person("Diana", 25, (OperatingSystem.MACOS,)),
20+
Person("Ethan", 31, (OperatingSystem.UBUNTU, OperatingSystem.ARCH)),
21+
Person("Fiona", 27, (OperatingSystem.MACOS, OperatingSystem.UBUNTU)),
22+
Person("George", 22, (OperatingSystem.ARCH, OperatingSystem.MACOS)),
23+
Person("Zara", 33, (OperatingSystem.ARCH, OperatingSystem.MACOS))
24+
]
25+
26+
class TestLaptopAllocation(unittest.TestCase):
27+
28+
def setUp(self):
29+
self.assignment = allocate_laptops(people, laptops)
30+
31+
def test_everyone_has_laptop(self):
32+
assigned_people = set(self.assignment.keys())
33+
self.assertEqual(assigned_people, set(people), "Some people are missing a laptop")
34+
35+
def test_no_duplicate_laptops(self):
36+
assigned_laptop_ids = [laptop.id for laptop in self.assignment.values()]
37+
self.assertEqual(len(assigned_laptop_ids), len(set(assigned_laptop_ids)), "Duplicate laptops assigned")
38+
39+
def test_sadness_scores(self):
40+
for person, laptop in self.assignment.items():
41+
if laptop.operating_system in person.preferred_operating_system:
42+
score = person.preferred_operating_system.index(laptop.operating_system)
43+
self.assertGreaterEqual(score, 0, f"Negative score for {person.name}")
44+
else:
45+
self.assertEqual(unpreferred_OS_penalty, 100, f"Unexpected unpreferred penalty for {person.name}")
46+
47+
def test_total_sadness(self):
48+
total_sadness = sum(
49+
person.preferred_operating_system.index(laptop.operating_system)
50+
if laptop.operating_system in person.preferred_operating_system else unpreferred_OS_penalty
51+
for person, laptop in self.assignment.items()
52+
)
53+
calculated_sadness = sum(
54+
person.preferred_operating_system.index(laptop.operating_system)
55+
if laptop.operating_system in person.preferred_operating_system else unpreferred_OS_penalty
56+
for person, laptop in self.assignment.items()
57+
)
58+
self.assertEqual(total_sadness, calculated_sadness, "Total sadness mismatch")
59+
60+
if __name__ == "__main__":
61+
unittest.main()

0 commit comments

Comments
 (0)