Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions prep-exercises/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Virtual Environment
.venv/
venv/
.env/
env/
ENV/

# Python cache
__pycache__/
*.pyc
*.pyo
*.pyd

# mypy cache
.mypy_cache/

# IDE
.vscode/
.idea/
*.swp
*.swo

# OS
.DS_Store

104 changes: 104 additions & 0 deletions prep-exercises/laptop_allocation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
from dataclasses import dataclass
from enum import Enum
from typing import List, Dict

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.
# Using tuple instead of List to make Person hashable (for dict keys)
preferred_operating_system: tuple[OperatingSystem, ...]


@dataclass(frozen=True)
class Laptop:
id: int
manufacturer: str
model: str
screen_size_in_inches: float
operating_system: OperatingSystem


def calculate_sadness(person: Person, laptop: Laptop) -> int:
"""Calculate sadness score for a person-laptop pairing."""
try:
return person.preferred_operating_system.index(laptop.operating_system)
except ValueError:
return 100


def allocate_laptops(people: List[Person], laptops: List[Laptop]) -> Dict[Person, Laptop]:
"""
Allocate laptops to people minimizing total sadness.

Sadness is defined as:
- Index in preference list (0 for first choice, 1 for second, etc.)
- 100 if the OS is not in their preference list
"""
if len(people) != len(laptops):
raise ValueError("Number of people must equal number of laptops")

# Greedy approach: Sort people by how limited their good options are
# Then allocate their best available choice
allocation: Dict[Person, Laptop] = {}
available_laptops = list(laptops)

# Create a priority queue of (person, laptop, sadness) tuples
# Sort by sadness to allocate best matches first
preferences = []
for person in people:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this approach you are looping over all people and laptops,then afterwards sorting and then doing the assignement.

Is there a way you could achieve this with fewer loops, or not going fully around the loops you have so much?

Imagine if you were doing this for a class full of hundreds of laptops and people - how many times would you end up going around the loops?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For 100 people and 100 laptops:
Before: 10,000 pairs created + sorted
After: At most 10,000 sadness calculations, but typically much fewer due to early exits and shrinking sets

for laptop in available_laptops:
sadness = calculate_sadness(person, laptop)
preferences.append((sadness, person, laptop))

preferences.sort(key=lambda x: x[0])

# Greedy allocation: try to give everyone their best available choice
allocated_people = []
allocated_laptops = []

for sadness, person, laptop in preferences:
if person not in allocated_people and laptop not in allocated_laptops:
allocation[person] = laptop
allocated_people.append(person)
allocated_laptops.append(laptop)

if len(allocation) == len(people):
break

return allocation


def calculate_total_sadness(allocation: Dict[Person, Laptop]) -> int:
"""Calculate total sadness for an allocation."""
return sum(calculate_sadness(person, laptop) for person, laptop in allocation.items())


# Test the function
people = [
Person("Alice", 25, (OperatingSystem.UBUNTU, OperatingSystem.ARCH, OperatingSystem.MACOS)),
Person("Bob", 30, (OperatingSystem.MACOS, OperatingSystem.UBUNTU)),
Person("Charlie", 28, (OperatingSystem.ARCH, OperatingSystem.UBUNTU)),
]

laptops = [
Laptop(1, "Dell", "XPS", 13, OperatingSystem.UBUNTU),
Laptop(2, "Apple", "MacBook", 13, OperatingSystem.MACOS),
Laptop(3, "Lenovo", "ThinkPad", 14, OperatingSystem.ARCH),
]

allocation = allocate_laptops(people, laptops)

print("Laptop Allocation:")
for person, laptop in allocation.items():
sadness = calculate_sadness(person, laptop)
print(f"{person.name} -> {laptop.manufacturer} {laptop.model} ({laptop.operating_system.value}) - Sadness: {sadness}")

print(f"\nTotal Sadness: {calculate_total_sadness(allocation)}")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about efficiency again, you just looped over everyone to get their individual sadness. The function you're calling here uses a loop as well. Is there any other way of achieving this without needing to loop twice here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, with these changes:

  • Add total_sadness = 0 before the loop
  • Accumulate total_sadness += sadness inside the loop
  • Replace the calculate_total_sadness(allocation) call with total_sadness