diff --git a/.gitignore b/.gitignore index 3c3629e6..f006d252 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ node_modules +.venv +__pycache__ +*.pyc diff --git a/sprint-5/laptop_allocation.py b/sprint-5/laptop_allocation.py new file mode 100644 index 00000000..38107d15 --- /dev/null +++ b/sprint-5/laptop_allocation.py @@ -0,0 +1,89 @@ +from dataclasses import dataclass +from enum import Enum +from typing import List, Dict +import numpy as np +from scipy.optimize import linear_sum_assignment + +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") + + n = len(people) + cost_matrix = np.zeros((n, n), dtype=int) + + for i, person in enumerate(people): + for j, laptop in enumerate(laptops): + if laptop.operating_system in person.preferred_operating_system: + cost_matrix[i, j] = person.preferred_operating_system.index(laptop.operating_system) + else: + cost_matrix[i, j] = unpreferred_OS_penalty + + 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), + 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) + +print("Laptop Assignments:\n") + +for person, laptop in assignment.items(): + 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} → {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()