-
-
Notifications
You must be signed in to change notification settings - Fork 42
WestMidlands| SDC-NOV-2025| Sara Tahir| Sprint 5| Feature/laptop allocation #288
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
01e59e9
04009aa
3d1c0c8
f82feda
1620fd6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| { | ||
| "python-envs.pythonProjects": [] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| from dataclasses import dataclass | ||
| from enum import Enum | ||
| from typing import Dict, List | ||
| 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" | ||
| 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: tuple[OperatingSystem] | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class Laptop: | ||
| id: int | ||
| manufacturer: str | ||
| model: str | ||
| screen_size_in_inches: float | ||
| operating_system: OperatingSystem | ||
|
|
||
| #library of laptops | ||
| 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), | ||
| 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_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: | ||
| 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 [] | ||
| 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 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're using a "black box" algorithm here. It is entirely opaque what it is doing or how it is working. For me, this would trigger adding some test files to check that it is working how I expect it to.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. very good idea.I have added one very basic test to check this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Except that with one person and one laptop, it's hardly testing the algorithm. |
||
| # Map people to laptops | ||
| 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]] = laptops[j] | ||
| return allocation | ||
|
|
||
|
|
||
| def print_allocation(allocation: Dict[Person, Laptop]): | ||
| for person, lap in allocation.items(): | ||
| print(f"{person.name} gets Laptop {lap.id} ({lap.operating_system.value})") | ||
|
|
||
| allocation = allocate_laptops(people, laptops) | ||
| print_allocation(allocation) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| [pytest] | ||
| pythonpath = . |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
|
|
||
| #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 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dell have started making macBooks? who knew? 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well One day they might have a merger :O