-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathalgorithm.py
475 lines (384 loc) · 17.6 KB
/
algorithm.py
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
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
import operator
import csv
import threading
import concurrent.futures
from hashtable import Package, HashTable
from routing import Location, Map
from datetime import datetime, time
from threading import Thread
# Fields
time_of_day = time(8, 0)
end_of_day = time(17, 0)
delivered_together = set()
package_ready = []
def update_time() -> time:
"""Updates the time by one minute each time it's called.
Runtime complexity of O(1)."""
global time_of_day
update = 1
if time_of_day == end_of_day:
return False
else:
if (time_of_day.minute + update) >= 60:
time_of_day = time(
time_of_day.hour + 1,
(time_of_day.minute + update) % 60)
else:
time_of_day = time(time_of_day.hour, time_of_day.minute + update)
def distance_covered(location) -> int:
"""Calculate the distance covered using the truck's speed.
Runtime complexity of O(1)."""
per_hour = 60
truck_speed = 18
mile_per_minute = truck_speed / per_hour
distance = int(location.distance / mile_per_minute)
return distance
def delivery_time(location) -> time:
"""Updates the time the delivery of a package.
Runtime complexity of O(1)."""
per_hour = 60
truck_speed = 18
mile_per_minute = truck_speed / per_hour
time_to_deliver = int(location.distance / mile_per_minute)
if (time_of_day.minute + time_to_deliver) >= per_hour:
time_of_delivery = time(time_of_day.hour + 1,
(time_of_day.minute + time_to_deliver) % 60)
else:
time_of_delivery = time(time_of_day.hour,
time_of_day.minute + time_to_deliver)
return time_of_delivery
def return_to_hub(hashtable, hub_hashtable) -> None:
"""Returns the truck back home to package base.
Runtime complexity of O(n)."""
hub_map = create_map()
current_location = hashtable.location
hub_location = hub_hashtable.location
for location in hub_map.location_adjacency_list.keys():
if hub_location == location.address:
hub_location = location
if current_location == location.address:
current_location = location
routes_shortest_paths(hub_map, current_location)
per_hour = 60
truck_speed = 18
mile_per_minute = truck_speed / per_hour
time_to_return = int(hub_location.distance / mile_per_minute)
if (time_of_day.minute + time_to_return) >= per_hour:
time_of_return = time(time_of_day.hour + 1,
(time_of_day.minute + time_to_return) % 60)
else:
time_of_return = time(time_of_day.hour,
time_of_day.minute + time_to_return)
in_route = True
while in_route:
if time_of_day == end_of_day:
return False
if time_of_day >= time_of_return:
in_route = False
else:
with hashtable._lock:
update_time()
hashtable.location = hub_location.address
hashtable.distance += hub_location.distance
def load_packages() -> None:
"""Open file, read package items one line at a time,
create Package objects and append them to a list.
Return the list once the entire file is processed.
Runtime complexity of O(n^2)."""
global package_ready
with open('Package File.csv') as data:
for line in csv.reader(data):
package_list = [item for item in line]
package = Package(int(package_list[0]),
package_list[1],
package_list[2],
int(package_list[4]),
package_list[5],
int(package_list[6]),
package_list[7])
package.status = 'Ready For Shipment'
package_list = []
if package.notes[0:22] == "Must be delivered with":
together_id = package.notes[23:].split(",")
for item in together_id:
delivered_together.add(int(item))
delivered_together.add(package.id)
if (package.notes ==
"Delayed on flight" +
"---will not arrive to depot until 9:05 am"):
package.status = 'Delayed On Flight'
package_ready.append(package)
else:
package_ready.append(package)
def create_map() -> Map:
"""Creates a new map(hashtable) with it's location objects.
Runtime complexity of O(n^2)."""
map = Map()
distance_list = []
location_list = []
with open('Distance Table.csv') as data:
for line in csv.reader(data):
address = line.pop(0)
distance = [float(item) for item in line if item != '']
distance_list.append(distance)
location = Location(address)
map.add_location(location)
location_list.append(location)
for i, line in enumerate(location_list):
for j in range(len(distance_list[i]) - 1):
map.add_undirected_route(location_list[i],
location_list[j],
distance_list[i].pop(0))
return map
def routes_shortest_paths(map_graph, starting_location) -> None:
"""
This code uses Dijkstra Shortest Path Alogorithm
which assigns distance(weight) to a map's(graph)
routes(edges) and assigns the locations(vertex)
with a predecessor location(vertex) with the
smallest route distance(weighted edge).
Runtime complexity
of O(n^2).
"""
# List comprehenstion to store unvisited locations.
unvisited_locations = [current_location
for current_location
in map_graph.location_adjacency_list]
# Starting location is assigned a distance of 0
starting_location.distance = 0
# Location's distance and predecessor location
# attributes are updated for shortes path.
# Each while loop iteration removes a
# location from the unvisited_location queue.
while len(unvisited_locations) > 0:
# Iterate through the unvisited_locations for the
# smallest distance between locations. The location
# with the smallest distance will be popped from
# the unvisted location and be referenced for the
# location_adjacency_list to find the shortest path.
smallest_index = 0
for i in range(1, len(unvisited_locations)):
if (unvisited_locations[i].distance <
unvisited_locations[smallest_index].distance):
smallest_index = i
current_location = unvisited_locations.pop(smallest_index)
# Iterate through the current_location's adjancency list
# and uses the current_location and adjacent(the
# iterable value from the iteration) as the dictionary
# key for map_graph.route_distance which is used to find
# an alternative route distance if possible.
for adjacent in map_graph.location_adjacency_list[current_location]:
route_distance = map_graph.route_distance[(current_location,
adjacent)]
alternative_route_distance = (current_location.distance +
route_distance)
# If alernative_route_distance is less than the
# adjacent.distance then the adjacent_vertix
# distance and predecessor location atributes are
# updated with alternative_route_distance and
# current_location respectivily.
if alternative_route_distance < adjacent.distance:
adjacent.distance = alternative_route_distance
adjacent.pre_location = current_location
def next_delivery(hashtable) -> 'location':
"""Greedy algorithm that will select the package for the next delivery.
Runtime complexity of O(n^2)."""
closest_delivery_location = None
closest_delivery_distance = float('inf')
trucks_map = create_map()
current_location = hashtable.location
# Assign's location objects of associated packages
# to variables to find closest location.
package_location_list = []
for package in hashtable.table:
for location in trucks_map.location_adjacency_list.keys():
if package.address == location.address:
package_location_list.append(location)
if current_location == location.address:
current_location = location
# Removes duplicate locations
package_location_list = list(set(package_location_list))
routes_shortest_paths(trucks_map, current_location)
closest_deadline = sorted(hashtable.table,
key=operator.attrgetter("deadline")).pop(0)
# Finds closest location
for package_location in package_location_list:
if package_location.distance < closest_delivery_distance:
closest_delivery_location = package_location
closest_delivery_distance = package_location.distance
return closest_delivery_location
def update_package(hashtable) -> None:
"""Updates the package's address. Runtime complexity of O(n)."""
for package in hashtable.table:
if (time_of_day >= time(10, 20) and
package.notes == 'Wrong address listed'):
package.address = '410 S State St'
package.zipcode = 84111
package.notes = 'Wrong address has been corrected'
def deliver_package(location, hashtable) -> None:
"""Delivers the package and updates the package and the hashtables statutes.
Runtime complexity of O(n)."""
# Changes the status of the package to delivered.
for package in hashtable.table:
if (package.address == location.address and
package.notes != 'Wrong address listed'):
print("Package %s delivered at %s with a deadline of %s" %
(package.id,
datetime.strptime(time_of_day.strftime('%H:%M'),
'%H:%M').strftime('%I:%M%p'),
datetime.strptime(package.deadline.strftime('%H:%M'),
'%H:%M').strftime('%I:%M%p')))
package.status = 'Delivered'
hashtable.remove()
hashtable.location = location.address
hashtable.distance += location.distance
def load_together(hashtable, hub_hashtable) -> None:
"""Loads packages that are delivered together.
Runtime complexity of O(n)."""
sorted_packages = sorted(hub_hashtable.table,
key=operator.attrgetter("deadline"))
for package in sorted_packages:
if package.id in delivered_together:
if package.status == 'Ready For Shipment':
if hashtable.insert(package):
package.status = 'Out For Delivery'
def load_truck2_only(hashtable, hub_hashtable) -> None:
"""Load the packages from the hub to the truck.
Runtime complexity of O(n)."""
sorted_packages = sorted(hub_hashtable.table,
key=operator.attrgetter("deadline"))
for package in sorted_packages:
if package.status == 'Ready For Shipment':
if hashtable.id == 2 and package.notes == "Can only be on truck 2":
if hashtable.insert(package):
package.status = 'Out For Delivery'
else:
break
def load_trucks(hashtable, hub_hashtable) -> None:
"""Load the packages from the hub to the truck.
Runtime complexity of O(n)."""
sorted_packages = sorted(hub_hashtable.table,
key=operator.attrgetter("deadline"))
for package in sorted_packages:
with hashtable._lock:
if package.status == 'Ready For Shipment':
if hashtable.insert(package):
package.status = 'Out For Delivery'
def expedite_package(hashtable, hub_hashtable) -> None:
"""Load truck with a deadline first. Runtime complexity of O(n)."""
sorted_packages = sorted(hub_hashtable.table,
key=operator.attrgetter("deadline"))
expedited_only = [package
for package in sorted_packages
if package.deadline <= time(10, 30)]
for package in expedited_only:
with hashtable._lock:
if package.status == 'Ready For Shipment':
if hashtable.insert(package):
package.status = 'Out For Delivery'
def route_trucks(truck_hashtable) -> bool:
"""Routes the trucks for delivery if it contains packages.
Runtime complexity of O(n)."""
for package in truck_hashtable.table:
if package is truck_hashtable.EMPTY_SINCE_START:
pass
else:
return True
return False
def packages_delivered(hub_hashtable) -> bool:
"""End the program once all packages are delivered.
Runtime complexity of O(n)."""
global time_of_day
global end_of_day
for package in hub_hashtable.table:
if package.status != 'Delivered':
return True
else:
pass
end_of_day = time_of_day
return False
def arrived_packages(hub_hashtable) -> None:
"""Sets the delayed package for ready for shipment.
Runtime complexity of O(n)."""
time_of_arrival = time(9, 5)
if time_of_day == time_of_arrival:
for package in hub_hashtable.table:
if package.status == 'Delayed On Flight':
package.status = 'Ready For Shipment'
def run_algorithm(truck_hashtable, hub_hashtable) -> None:
"""Runs the core algorithm for the routing program.
Runtime complexity of O(1)."""
while(packages_delivered(hub_hashtable)):
if time_of_day >= end_of_day:
return False
return_to_hub(truck_hashtable, hub_hashtable)
if time_of_day <= time(10, 30):
load_together(truck_hashtable, hub_hashtable)
expedite_package(truck_hashtable, hub_hashtable)
while(route_trucks(truck_hashtable)):
next_location = next_delivery(truck_hashtable)
time_to_deliver = delivery_time(next_location)
in_delivery = True
while in_delivery:
arrived_packages(hub_hashtable)
if time_of_day >= end_of_day:
return False
if time_of_day >= time_to_deliver:
deliver_package(next_location, truck_hashtable)
in_delivery = False
else:
with truck_hashtable._lock:
update_time()
update_package(hub_hashtable)
else:
load_truck2_only(truck_hashtable, hub_hashtable)
load_trucks(truck_hashtable, hub_hashtable)
while(route_trucks(truck_hashtable)):
next_location = next_delivery(truck_hashtable)
time_to_deliver = delivery_time(next_location)
in_delivery = True
while in_delivery:
arrived_packages(hub_hashtable)
if time_of_day >= end_of_day:
return False
if time_of_day >= time_to_deliver:
deliver_package(next_location, truck_hashtable)
in_delivery = False
else:
with truck_hashtable._lock:
update_time()
update_package(hub_hashtable)
def look_up(hour=17, minute=0, attribute=None, element=None) -> str:
"""Look-up function that runs the program and returns the results.
Runtime complexity of O(n)."""
global end_of_day
end_of_day = time(hour, minute)
# Location of the package storage facility.
hub_storage = HashTable(40)
# A truck that can deliver, tow and talk. What more can you ask??
mater_tow_truck = HashTable(16)
mater_tow_truck.id = 1
# 2scary4me
maximum_overdrive_truck = HashTable(16)
maximum_overdrive_truck.id = 2
# I tried moving this truck but it won't budge. I wonder what's below...
mew_truck = HashTable(151)
mew_truck.id = 151
load_packages()
for package in package_ready:
hub_storage.insert(package)
# Initialize the trucks to be used concurrently.
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
executor.submit(run_algorithm, mater_tow_truck, hub_storage)
executor.submit(run_algorithm, maximum_overdrive_truck, hub_storage)
hub_storage.distance = (mater_tow_truck.distance +
maximum_overdrive_truck.distance)
print(hub_storage.search(attribute, element))
print("First truck stopped with %s miles at %s." %
(mater_tow_truck.distance, mater_tow_truck.location))
print("Second truck stopped with %s miles at %s." %
(maximum_overdrive_truck.distance, maximum_overdrive_truck.location))
print("The time is %s." %
datetime.strptime(
time_of_day.strftime('%H:%M'), '%H:%M').strftime('%I:%M%p'))
print("The total milage is %s" % (hub_storage.distance))