-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgreedy_optimizer.py
More file actions
305 lines (247 loc) · 11.2 KB
/
greedy_optimizer.py
File metadata and controls
305 lines (247 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
"""
Greedy Algorithms Assignment
Implement three greedy algorithms for delivery optimization.
"""
import json
import time
# ============================================================================
# PART 1: PACKAGE PRIORITIZATION (Activity Selection)
# ============================================================================
def maximize_deliveries(time_windows):
"""
Schedule the maximum number of deliveries given time window constraints.
This is the activity selection problem. Each delivery has a start and end time.
You can only do one delivery at a time. A new delivery can start when the
previous one ends.
Args:
time_windows (list): List of dicts with 'delivery_id', 'start', 'end'
Returns:
list: List of delivery_ids that can be completed (maximum number possible)
Example:
time_windows = [
{'delivery_id': 'A', 'start': 1, 'end': 3},
{'delivery_id': 'B', 'start': 2, 'end': 5},
{'delivery_id': 'C', 'start': 4, 'end': 7}
]
maximize_deliveries(time_windows) returns ['A', 'C']
"""
# TODO: Implement greedy algorithm for activity selection
# Hint: What greedy choice gives you the most room for future deliveries?
# Hint: Think about sorting by a specific attribute
pass # Delete this and write your code
# ============================================================================
# PART 2: TRUCK LOADING (Fractional Knapsack)
# ============================================================================
def optimize_truck_load(packages, weight_limit):
"""
Maximize total priority value of packages loaded within weight constraint.
This is the fractional knapsack problem. You can take fractions of packages
(e.g., deliver part of a package). Goal is to maximize priority value while
staying within the weight limit.
Args:
packages (list): List of dicts with 'package_id', 'weight', 'priority'
weight_limit (int): Maximum weight the truck can carry
Returns:
dict: {
'total_priority': float (total priority value loaded),
'total_weight': float (total weight loaded),
'packages': list of dicts with 'package_id' and 'fraction' (how much of package taken)
}
Example:
packages = [
{'package_id': 'A', 'weight': 10, 'priority': 60},
{'package_id': 'B', 'weight': 20, 'priority': 100},
{'package_id': 'C', 'weight': 30, 'priority': 120}
]
weight_limit = 50
optimize_truck_load(packages, 50) returns packages A (full), B (full), C (partial)
"""
# TODO: Implement greedy algorithm for fractional knapsack
# Hint: What ratio determines which packages are most valuable per pound?
# Hint: You can take fractions - if you have 5 lbs capacity left and a 10 lb package, take 0.5 of it
pass # Delete this and write your code
# ============================================================================
# PART 3: DRIVER ASSIGNMENT (Interval Scheduling)
# ============================================================================
def minimize_drivers(deliveries):
"""
Assign deliveries to the minimum number of drivers needed.
Each delivery has a start and end time. A driver can do a delivery if it
doesn't overlap with their other assigned deliveries. Goal is to use the
fewest drivers possible.
Args:
deliveries (list): List of dicts with 'delivery_id', 'start', 'end'
Returns:
dict: {
'num_drivers': int (minimum drivers needed),
'assignments': list of lists (each sublist is one driver's deliveries)
}
Example:
deliveries = [
{'delivery_id': 'A', 'start': 1, 'end': 3},
{'delivery_id': 'B', 'start': 2, 'end': 4},
{'delivery_id': 'C', 'start': 5, 'end': 7}
]
minimize_drivers(deliveries) returns 2 drivers: [[A, C], [B]]
"""
# TODO: Implement greedy algorithm for interval scheduling
# Hint: How do you know if a delivery overlaps with another?
# Hint: Can you assign a delivery to an existing driver, or do you need a new one?
pass # Delete this and write your code
# ============================================================================
# TESTING & BENCHMARKING
# ============================================================================
def load_scenario(filename):
"""Load a scenario from JSON file."""
with open(f"scenarios/{filename}", "r") as f:
return json.load(f)
def test_package_prioritization():
"""Test package prioritization with small known cases."""
print("="*70)
print("TESTING PACKAGE PRIORITIZATION")
print("="*70 + "\n")
# Test case 1: Non-overlapping deliveries (should select all)
test1 = [
{'delivery_id': 'A', 'start': 1, 'end': 2},
{'delivery_id': 'B', 'start': 3, 'end': 4},
{'delivery_id': 'C', 'start': 5, 'end': 6}
]
result1 = maximize_deliveries(test1)
print(f"Test 1: Non-overlapping")
print(f" Expected: 3 deliveries")
print(f" Got: {len(result1)} deliveries")
print(f" {'✓ PASS' if len(result1) == 3 else '✗ FAIL'}\n")
# Test case 2: All overlapping (should select 1)
test2 = [
{'delivery_id': 'A', 'start': 1, 'end': 5},
{'delivery_id': 'B', 'start': 2, 'end': 4},
{'delivery_id': 'C', 'start': 3, 'end': 6}
]
result2 = maximize_deliveries(test2)
print(f"Test 2: All overlapping")
print(f" Expected: 1-2 deliveries (depends on greedy choice)")
print(f" Got: {len(result2)} deliveries")
print(f" Result: {result2}\n")
# Test case 3: Mixed overlapping
test3 = [
{'delivery_id': 'A', 'start': 1, 'end': 3},
{'delivery_id': 'B', 'start': 2, 'end': 5},
{'delivery_id': 'C', 'start': 4, 'end': 7},
{'delivery_id': 'D', 'start': 6, 'end': 9}
]
result3 = maximize_deliveries(test3)
print(f"Test 3: Mixed overlapping")
print(f" Expected: 2 deliveries (A ends at 3, C starts at 4)")
print(f" Got: {len(result3)} deliveries")
print(f" Result: {result3}")
print(f" {'✓ PASS' if len(result3) == 2 else '✗ FAIL'}\n")
def test_truck_loading():
"""Test truck loading with small known cases."""
print("="*70)
print("TESTING TRUCK LOADING")
print("="*70 + "\n")
# Test case 1: All packages fit
packages1 = [
{'package_id': 'A', 'weight': 10, 'priority': 60},
{'package_id': 'B', 'weight': 20, 'priority': 100}
]
result1 = optimize_truck_load(packages1, 50)
print(f"Test 1: All packages fit")
print(f" Expected: Total priority = 160, weight = 30")
print(f" Got: Priority = {result1['total_priority']}, weight = {result1['total_weight']}")
print(f" {'✓ PASS' if result1['total_priority'] == 160 else '✗ FAIL'}\n")
# Test case 2: Fractional required
packages2 = [
{'package_id': 'A', 'weight': 10, 'priority': 60}, # ratio = 6.0
{'package_id': 'B', 'weight': 20, 'priority': 100}, # ratio = 5.0
{'package_id': 'C', 'weight': 30, 'priority': 120} # ratio = 4.0
]
result2 = optimize_truck_load(packages2, 50)
print(f"Test 2: Fractional knapsack")
print(f" Expected: Take A (full), B (full), C (partial)")
print(f" Expected priority: ~240 (60 + 100 + 80 from 2/3 of C)")
print(f" Got: Priority = {result2['total_priority']}, weight = {result2['total_weight']}")
print(f" Packages: {result2['packages']}\n")
def test_driver_assignment():
"""Test driver assignment with small known cases."""
print("="*70)
print("TESTING DRIVER ASSIGNMENT")
print("="*70 + "\n")
# Test case 1: All non-overlapping (need 1 driver)
deliveries1 = [
{'delivery_id': 'A', 'start': 1, 'end': 2},
{'delivery_id': 'B', 'start': 3, 'end': 4},
{'delivery_id': 'C', 'start': 5, 'end': 6}
]
result1 = minimize_drivers(deliveries1)
print(f"Test 1: Non-overlapping")
print(f" Expected: 1 driver")
print(f" Got: {result1['num_drivers']} drivers")
print(f" {'✓ PASS' if result1['num_drivers'] == 1 else '✗ FAIL'}\n")
# Test case 2: All overlapping (need 3 drivers)
deliveries2 = [
{'delivery_id': 'A', 'start': 1, 'end': 5},
{'delivery_id': 'B', 'start': 2, 'end': 6},
{'delivery_id': 'C', 'start': 3, 'end': 7}
]
result2 = minimize_drivers(deliveries2)
print(f"Test 2: All overlapping")
print(f" Expected: 3 drivers")
print(f" Got: {result2['num_drivers']} drivers")
print(f" {'✓ PASS' if result2['num_drivers'] == 3 else '✗ FAIL'}\n")
# Test case 3: Mixed
deliveries3 = [
{'delivery_id': 'A', 'start': 1, 'end': 3},
{'delivery_id': 'B', 'start': 2, 'end': 4},
{'delivery_id': 'C', 'start': 4, 'end': 6},
{'delivery_id': 'D', 'start': 5, 'end': 7}
]
result3 = minimize_drivers(deliveries3)
print(f"Test 3: Mixed overlapping")
print(f" Expected: 2 drivers")
print(f" Got: {result3['num_drivers']} drivers")
print(f" Assignments: {result3['assignments']}")
print(f" {'✓ PASS' if result3['num_drivers'] == 2 else '✗ FAIL'}\n")
def benchmark_scenarios():
"""Benchmark all algorithms on realistic scenarios."""
print("\n" + "="*70)
print("BENCHMARKING ON REALISTIC SCENARIOS")
print("="*70 + "\n")
# Benchmark package prioritization
print("Scenario 1: Package Prioritization (50 deliveries)")
print("-" * 70)
data = load_scenario("package_prioritization.json")
start = time.perf_counter()
result = maximize_deliveries(data)
elapsed = time.perf_counter() - start
print(f" Deliveries scheduled: {len(result)} out of {len(data)}")
print(f" Runtime: {elapsed*1000:.4f} ms\n")
# Benchmark truck loading
print("Scenario 2: Truck Loading (100 packages, 500 lb limit)")
print("-" * 70)
data = load_scenario("truck_loading.json")
start = time.perf_counter()
result = optimize_truck_load(data['packages'], data['truck_capacity'])
elapsed = time.perf_counter() - start
print(f" Total priority loaded: {result['total_priority']:.2f}")
print(f" Total weight loaded: {result['total_weight']:.2f} lbs")
print(f" Packages used: {len(result['packages'])}")
print(f" Runtime: {elapsed*1000:.4f} ms\n")
# Benchmark driver assignment
print("Scenario 3: Driver Assignment (60 deliveries)")
print("-" * 70)
data = load_scenario("driver_assignment.json")
start = time.perf_counter()
result = minimize_drivers(data)
elapsed = time.perf_counter() - start
print(f" Drivers needed: {result['num_drivers']}")
print(f" Runtime: {elapsed*1000:.4f} ms\n")
if __name__ == "__main__":
print("GREEDY ALGORITHMS ASSIGNMENT - STARTER CODE")
print("Implement the greedy functions above, then run tests.\n")
# Uncomment these as you complete each part:
# test_package_prioritization()
# test_truck_loading()
# test_driver_assignment()
# benchmark_scenarios()
print("\n⚠ Uncomment the test functions in the main block to run tests!")