44from typing import List , Dict
55
66class OperatingSystem (Enum ):
7+ """enumeration of available operating systems."""
78 MACOS = "macOS"
89 ARCH = "Arch Linux"
910 UBUNTU = "Ubuntu"
1011
1112@dataclass (frozen = True )
1213class Person :
14+ """represents a person with a name, age, and their OS preferences."""
1315 name : str
1416 age : int
15- # Sorted in order of preference, most preferred is first.
17+ # listed in order of preference
1618 preferred_operating_systems : List [OperatingSystem ]
1719
1820
1921@dataclass (frozen = True )
2022class Laptop :
23+ """represents a laptop with specifications and an operating system."""
2124 id : int
2225 manufacturer : str
2326 model : str
@@ -41,10 +44,7 @@ class Laptop:
4144 Laptop (id = 3 , manufacturer = "Dell" , model = "XPS" , screen_size_in_inches = 15 , operating_system = OperatingSystem .UBUNTU ),
4245 Laptop (id = 4 , manufacturer = "Apple" , model = "MacBook" , screen_size_in_inches = 13 , operating_system = OperatingSystem .MACOS ),
4346 Laptop (id = 5 , manufacturer = "Apple" , model = "MacBook Air" , screen_size_in_inches = 13 , operating_system = OperatingSystem .MACOS ),
44- Laptop (id = 6 , manufacturer = "Lenovo" , model = "ThinkPad" , screen_size_in_inches = 14 , operating_system = OperatingSystem .ARCH ),
45- Laptop (id = 7 , manufacturer = "Asus" , model = "ZenBook" , screen_size_in_inches = 13 , operating_system = OperatingSystem .UBUNTU ),
46- Laptop (id = 8 , manufacturer = "HP" , model = "Spectre" , screen_size_in_inches = 14 , operating_system = OperatingSystem .MACOS ),
47- Laptop (id = 9 , manufacturer = "Apple" , model = "MacBook Pro" , screen_size_in_inches = 16 , operating_system = OperatingSystem .MACOS ),
47+ Laptop (id = 6 , manufacturer = "HP" , model = "Spectre" , screen_size_in_inches = 14 , operating_system = OperatingSystem .MACOS ),
4848]
4949
5050people : List [Person ] = [
@@ -56,7 +56,11 @@ class Laptop:
5656]
5757
5858def user_prompt () -> Person :
59+ """
60+ prompt the user to input their details and preferred operating systems
61+ """
5962 try :
63+
6064 # strip() whitespace before processing (no need for str type here as input always returns a string)
6165 name = input ("Please enter your first name: " ).strip ()
6266 if not name .isalpha ():
@@ -76,15 +80,18 @@ def user_prompt() -> Person:
7680 preferred_os = input (f"Please enter your preferred operating systems in order of preference, separated by commas (e.g., { ', ' .join (valid_os )} ): " ).strip ()
7781
7882 # split and validate the OS
79- preferred_os_list = [os .strip () for os in preferred_os .split ("," )]
83+ preferred_os_list = [os .strip () for os in preferred_os .split ("," ) if os .strip ()]
84+ if not preferred_os_list :
85+ raise ValueError ("You must enter at least one operating system." )
86+
8087 preferred_os_enum = []
8188 for os_name in preferred_os_list :
8289 if os_name not in valid_os :
8390 raise ValueError (f"Invalid operating system: { os_name } " )
8491 # convert to enum
8592 preferred_os_enum .append (OperatingSystem (os_name ))
8693
87- return Person (name = name , age = age , preferred_operating_systems = preferred_os_enum )
94+ return Person (name = name , age = age , preferred_operating_systems = tuple ( preferred_os_enum ) )
8895
8996 # throw an error and exit for invalid age and os input
9097 except ValueError as error :
@@ -93,14 +100,59 @@ def user_prompt() -> Person:
93100
94101
95102def find_possible_laptops (available_laptops : List [Laptop ], current_person : Person ) -> List [Laptop ]:
103+ """
104+ find laptops that match a person's preferred operating systems.
105+ """
96106 return [
97107 laptop for laptop in available_laptops
98108 if laptop .operating_system in current_person .preferred_operating_systems
99109 ]
100110
101- # allocated sequentially, in a first come first served order
102- def allocate_laptops_sequentially (people : List [Person ], laptops : List [Laptop ]) -> Dict [Person , Laptop ]:
103- """
104- Allocate laptops to people sequentially based on the order in the people list.
105- This approach respects the 'wait your turn' principle.
106- """
111+
112+ def allocate_laptops (people : List [Person ], laptops : List [Laptop ]) -> Dict [Person , Laptop ]:
113+ """
114+ allocate laptops to people to minimize total sadness.
115+ """
116+ def calculate_sadness_score (person : Person , laptop : Laptop ) -> int :
117+ try :
118+ return person .preferred_operating_systems .index (laptop .operating_system )
119+ except ValueError :
120+ return 100
121+
122+ allocated_laptops = {}
123+
124+ # create a shallow copy of the laptops list
125+ available_laptops = laptops [:]
126+
127+ for person in people :
128+ # ensure available_laptops is not empty before calling min
129+ if not available_laptops :
130+ raise ValueError ("No laptops available to allocate." )
131+
132+ # use min() to select the laptop with the lowest sadness score for the person.
133+ # lambda function is scoring the person's preferences by calculating the "sadness score" for each laptop based on the index position
134+ best_laptop = min (available_laptops , key = lambda laptop : calculate_sadness_score (person , laptop ), default = None )
135+ if best_laptop :
136+ allocated_laptops [person .name ] = best_laptop
137+ available_laptops .remove (best_laptop )
138+
139+
140+ if len (allocated_laptops ) != len (people ):
141+ raise ValueError ("Not enough laptops to allocate one to each person." )
142+
143+ return allocated_laptops
144+
145+
146+ def main ():
147+ # Prompt the user for their details and add them to the people list
148+ new_person = user_prompt ()
149+ people .append (new_person )
150+
151+ # Allocate laptops and print the results
152+ allocation = allocate_laptops (people , laptops_list )
153+ for name , laptop in allocation .items ():
154+ print (f"{ name } was allocated { laptop .manufacturer } { laptop .model } with { laptop .operating_system .value } " )
155+
156+ # Ensure the script runs only when executed directly
157+ if __name__ == "__main__" :
158+ main ()
0 commit comments