-
-
Notifications
You must be signed in to change notification settings - Fork 42
London | 25-SDC-Nov | Zohreh Kazemianpour | Sprint 5 | Laptop-allocation #314
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
7671cbc
ffdcea1
333ea48
a0b9fcd
76f824d
3cedf09
633a08e
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 |
|---|---|---|
| @@ -1 +1,2 @@ | ||
| node_modules | ||
| .venv |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| from dataclasses import dataclass | ||
| from enum import Enum | ||
| from typing import List | ||
| from typing import Dict | ||
|
|
||
| # ============================================================================ | ||
| # ENUM DEFINITIONS | ||
| # ============================================================================ | ||
| class OperatingSystem(Enum): | ||
| MACOS = "macOS" | ||
| ARCH = "Arch Linux" | ||
| UBUNTU = "Ubuntu" | ||
|
|
||
| # ============================================================================ | ||
| # DATA CLASSES | ||
| # ============================================================================ | ||
| @dataclass(frozen=True) | ||
| class Person: | ||
| name: str | ||
| age: int | ||
| # Sorted in order of preference, most preferred is first. | ||
| preferred_operating_system: tuple | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class Laptop: | ||
| id: int | ||
| manufacturer: str | ||
| model: str | ||
| screen_size_in_inches: float | ||
| operating_system: OperatingSystem | ||
|
|
||
| # ============================================================================ | ||
| # HELPER FUNCTION | ||
| # ============================================================================ | ||
| """ | ||
| Calculates how "sad" a person would be with a given laptop allocation. | ||
|
|
||
| Sadness is defined as the position (0-indexed) of the laptop's OS | ||
| in the person's preference list. For example: | ||
| - If preferences are [UBUNTU, ARCH, MACOS] and they get UBUNTU: sadness = 0 | ||
| - If preferences are [UBUNTU, ARCH, MACOS] and they get ARCH: sadness = 1 | ||
| - If preferences are [UBUNTU, ARCH, MACOS] and they get MACOS: sadness = 2 | ||
| - If they get an OS not in their preferences: sadness = 100 | ||
|
|
||
| Args: | ||
| person: The person receiving the laptop | ||
| laptop: The laptop being allocated | ||
|
|
||
| Returns: | ||
| An integer representing sadness (0 = most happy, 100 = not in preferences) | ||
| """ | ||
|
|
||
| def calculate_sadness(person:Person, laptop:Laptop)-> int: | ||
| if laptop.operating_system in person.preferred_operating_system: | ||
| # Find the index (position) of this OS in their preference list | ||
| sadness = person.preferred_operating_system.index(laptop.operating_system) | ||
| return sadness | ||
| else: | ||
| # OS not in preferences = very sad | ||
| return 100 | ||
|
|
||
| # ============================================================================ | ||
| # TEST DATA | ||
| # ============================================================================ | ||
|
|
||
| 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 Pro", screen_size_in_inches=16, | ||
| operating_system=OperatingSystem.MACOS), | ||
| Laptop(id=6, manufacturer="Lenovo", model="ThinkPad", screen_size_in_inches=14, | ||
| operating_system=OperatingSystem.UBUNTU), | ||
| Laptop(id=7, manufacturer="System76", model="Lemur Pro", screen_size_in_inches=15, | ||
| operating_system=OperatingSystem.ARCH), | ||
| Laptop(id=8, manufacturer="Framework", model="Framework 13", screen_size_in_inches=13, | ||
| operating_system=OperatingSystem.UBUNTU), | ||
| ] | ||
|
|
||
| people = [ | ||
| Person(name="Imran", age=22, preferred_operating_system=(OperatingSystem.UBUNTU, OperatingSystem.MACOS)), | ||
| Person(name="Eliza", age=34, preferred_operating_system=(OperatingSystem.ARCH, OperatingSystem.UBUNTU)), | ||
| Person(name="Marcus", age=28, preferred_operating_system=(OperatingSystem.MACOS, OperatingSystem.UBUNTU)), | ||
| Person(name="Sofia", age=31, preferred_operating_system=(OperatingSystem.UBUNTU,)), | ||
| Person(name="James", age=25, preferred_operating_system=(OperatingSystem.ARCH, OperatingSystem.MACOS)), | ||
| Person(name="Nina", age=29, preferred_operating_system=(OperatingSystem.MACOS, OperatingSystem.ARCH, OperatingSystem.UBUNTU)), | ||
| ] | ||
|
|
||
|
|
||
|
|
||
| # ============================================================================ | ||
| # TESTING | ||
| # ============================================================================ | ||
|
|
||
| def allocate_laptops(people: List[Person], laptops: List[Laptop]) -> Dict[Person,Laptop]: | ||
|
|
||
| if len(laptops) < len(people): | ||
| raise ValueError("Not enough laptops to allocate one per person") | ||
|
|
||
|
|
||
| people_sorted = sorted(people, key = lambda p: len(p.preferred_operating_system)) | ||
|
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. Nice touch - haven't seen anyone else do this, but it is a clever tweak to the algorithm
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. Thank you! I looked into more complex approaches like the Hungarian Algorithm, but found it a bit overwhelming at this stage for me. I decided to stick with greedy approach to ensure the pickest user get a compatible laptop before the more flexible users. With this we can minimise the overall sadness score for the group. |
||
|
|
||
| result={} | ||
| available_laptops = laptops.copy() | ||
|
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. Good spot that you need the copy if you don't want to change the original |
||
|
|
||
| for person in people_sorted: | ||
| smallest_sadness = float("inf") | ||
| best_laptop = None | ||
|
|
||
| for laptop in available_laptops: | ||
| sadness = calculate_sadness(person, laptop) | ||
| if sadness < smallest_sadness: | ||
| smallest_sadness = sadness | ||
| best_laptop = 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 could of course break out of the loop if sadness is 0
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. That is a very good point, thanks for mentioning it. I've added a break when sadness == 0 to avoid unnecessary iterations. |
||
|
|
||
|
|
||
| if smallest_sadness == 0: | ||
| break | ||
|
|
||
| result[person]= best_laptop | ||
| available_laptops.remove(best_laptop) | ||
|
|
||
| return result | ||
|
|
||
|
|
||
| allocation = allocate_laptops(people, laptops) | ||
|
|
||
| print("\n" + "="*50) | ||
| print("FINAL ALLOCATION:") | ||
| print("="*50) | ||
| for person, laptop in allocation.items(): | ||
| print(f"{person.name}: {laptop.manufacturer} {laptop.model} ({laptop.operating_system.value})") | ||
|
|
||
|
|
||
|
|
||
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.
Why a tuple rather than a list?
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.
I used a tuple because Person needs to be hashable to serve as a dictionary key. If I used a list, Python would raise a TypeError: unhashable type: list
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.
My version of python doesn't complain. What Python version are you using?
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.
Ah, OK, I now understand it. The data being passed in is Tuples, so when I have preferred_operating_system as a list, Python ignores the type hint and stores the Tuple instead. I'm too used to strongly typed languages!
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.
Take a gold star. Your Python knowledge exceeds mine in this respect (and probably many others, I'm guessing)