diff --git a/AssignmentData.py b/AssignmentData.py new file mode 100644 index 0000000..077cada --- /dev/null +++ b/AssignmentData.py @@ -0,0 +1,138 @@ +from DeliveryObject import DeliveryObject + +# Names from the Data Tables provided +xyNames = [ +"Western Governors University 4001 South 700 East, Salt Lake City, UT 84107", +"International Peace Gardens 1060 Dalton Ave S", +"Sugar House Park 1330 2100 S", +"Taylorsville-Bennion Heritage City Gov Off 1488 4800 S", +"Salt Lake City, Division of Health Services 177 W Price Ave", +"South Salt Lake Public Works 195 W Oakland Ave", +"Salt Lake City, Streets and Sanitation 2010 W 500 S", +"Deker Lake 2300 Parkway Blvd", +"Salt Lake City,Ottinger Hall 233 Canyon Rd", +"Columbus Library 2530 S 500 E", +"Taylorsville City Hall 2600 Taylorsville Blvd", +"South Salt Lake Police 2835 Main St", +"Council Hall 300 State St", +"Redwood Park 3060 Lester St", +"Salt Lake County Mental Health 3148 S 1100 W", +"Salt Lake County/United Police Dept 3365 S 900 W", +"West Valley Prosecutor 3575 W Valley Central Sta bus Loop", +"Housing Auth. of Salt Lake County 3595 Main St", +"Utah DMV Administrative Office 380 W 2880 S", +"Third District Juvenile Court 410 S State St", +"Cottonwood Regional Softball Complex 4300 S 1300 E", +"Holiday City Office 4580 S 2300 E", +"Murray City Museum 5025 State St", +"Valley Regional Softball Complex 5100 South 2700 West", +"City Center of Rock Springs 5383 South 900 East #104", +"Rice Terrace Pavilion Park 600 E 900 South", +"Wheeler Historic Farm 6351 South 900 East", +] + +# Names from the Data Tables provided +xyNames2 = [ +"HUB", +"1060 Dalton Ave S (84104)", +"1330 2100 S (84106)", +"1488 4800 S (84123)", +"177 W Price Ave (84115)", +"195 W Oakland Ave (84115)", +"2010 W 500 S (84104)", +"2300 Parkway Blvd (84119)", +"233 Canyon Rd (84103)", +"2530 S 500 E (84106)", +"2600 Taylorsville Blvd (84118)", +"2835 Main St (84115)", +"300 State St (84103)", +"3060 Lester St (84119)", +"3148 S 1100 W (84119)", +"3365 S 900 W (84119)", +"3575 W Valley Central Station bus Loop (84119)", +"3595 Main St (84115)", +"380 W 2880 S (84115)", +"410 S State St (84111)", +"4300 S 1300 E (84117)", +"4580 S 2300 E (84117)", +"5025 State St (84107)", +"5100 South 2700 West (84118)", +"5383 S 900 East #104 (84117)", +"600 E 900 South (84105)", +"6351 South 900 East (84121)" +] + +# Data provided from assignment info +distanceChart = [ +[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], +[7.2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], +[3.8, 7.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], +[11.0, 6.4, 9.2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], +[2.2, 6.0, 4.4, 5.6, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], +[3.5, 4.8, 2.8, 6.9, 1.9, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], +[10.9, 1.6, 8.6, 8.6, 7.9, 6.3, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], +[8.6, 2.8, 6.3, 4.0, 5.1, 4.3, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], +[7.6, 4.8, 5.3, 11.1, 7.5, 4.5, 4.2, 7.7, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], +[2.8, 6.3, 1.6, 7.3, 2.6, 1.5, 8.0, 9.3, 4.8, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], +[6.4, 7.3, 10.4, 1.0, 6.5, 8.7, 8.6, 4.6, 11.9, 9.4, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], +[3.2, 5.3, 3.0, 6.4, 1.5, 0.8, 6.9, 4.8, 4.7, 1.1, 7.3, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], +[7.6, 4.8, 5.3, 11.1, 7.5, 4.5, 4.2, 7.7, 0.6, 5.1, 12.0, 4.7, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], +[5.2, 3.0, 6.5, 3.9, 3.2, 3.9, 4.2, 1.6, 7.6, 4.6, 4.9, 3.5, 7.3, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], +[4.4, 4.6, 5.6, 4.3, 2.4, 3.0, 8.0, 3.3, 7.8, 3.7, 5.2, 2.6, 7.8, 1.3, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], +[3.7, 4.5, 5.8, 4.4, 2.7, 3.8, 5.8, 3.4, 6.6, 4.0, 5.4, 2.9, 6.6, 1.5, 0.6, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], +[7.6, 7.4, 5.7, 7.2, 1.4, 5.7, 7.2, 3.1, 7.2, 6.7, 8.1, 6.3, 7.2, 4.0, 6.4, 5.6, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], +[2.0, 6.0, 4.1, 5.3, 0.5, 1.9, 7.7, 5.1, 5.9, 2.3, 6.2, 1.2, 5.9, 3.2, 2.4, 1.6, 7.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], +[3.6, 5.0, 3.6, 6.0, 1.7, 1.1, 6.6, 4.6, 5.4, 1.8, 6.9, 1.0, 5.4, 3.0, 2.2, 1.7, 6.1, 1.6, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], +[6.5, 4.8, 4.3, 10.6, 6.5, 3.5, 3.2, 6.7, 1.0, 4.1, 11.5, 3.7, 1.0, 6.9, 6.8, 6.4, 7.2, 4.9, 4.4, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], +[1.9, 9.5, 3.3, 5.9, 3.2, 4.9, 11.2, 8.1, 8.5, 3.8, 6.9, 4.1, 8.5, 6.2, 5.3, 4.9, 10.6, 3.0, 4.6, 7.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], +[3.4, 10.9, 5.0, 7.4, 5.2, 6.9, 12.7, 10.4, 10.3, 5.8, 8.3, 6.2, 10.3, 8.2, 7.4, 6.9, 12.0, 5.0, 6.6, 9.3, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0], +[2.4, 8.3, 6.1, 4.7, 2.5, 4.2, 10.0, 7.8, 7.8, 4.3, 4.1, 3.4, 7.8, 5.5, 4.6, 4.2, 9.4, 2.3, 3.9, 6.8, 2.9, 4.4, 0.0, 0.0, 0.0, 0.0], +[6.4, 6.9, 9.7, 0.6, 6.0, 9.0, 8.2, 4.2, 11.5, 7.8, 0.4, 6.9, 11.5, 4.4, 4.8, 5.6, 7.5, 5.5, 6.5, 11.4, 6.4, 7.9, 4.5, 0.0, 0.0, 0.0], +[2.4, 10.0, 6.1, 6.4, 4.2, 5.9, 11.7, 9.5, 9.5, 4.8, 4.9, 5.2, 9.5, 7.2, 6.3, 5.9, 11.1, 4.0, 5.6, 8.5, 2.8, 3.4, 1.7, 5.4, 0.0, 0.0], +[5.0, 4.4, 2.8, 10.1, 5.4, 3.5, 5.1, 6.2, 2.8, 3.2, 11.0, 3.7, 2.8, 6.4, 6.5, 5.7, 6.2, 5.1, 4.3, 1.8, 6.0, 7.9, 6.8, 10.6, 7.0, 0.0], +[3.6, 13.0, 7.4, 10.1, 5.5, 7.2, 14.2, 10.7, 14.1, 6.0, 6.8, 6.4, 14.1, 10.5, 8.8, 8.4, 13.6, 5.2, 6.9, 13.1, 4.1, 4.7, 3.1, 7.8, 1.3, 8.3] +] + +# Data provided from assignment info [ wrapped in DeliveryObject's ] +packages = [ +DeliveryObject(1, "195 W Oakland Ave", "Salt Lake City", "UT", 84115, "10:30 AM", 21), +DeliveryObject(2, "2530 S 500 E", "Salt Lake City", "UT", 84106, "EOD", 44), +DeliveryObject(3, "233 Canyon Rd", "Salt Lake City", "UT", 84103, "EOD", 2, "Can only be on truck 2"), +DeliveryObject(4, "380 W 2880 S", "Salt Lake City", "UT", 84115, "EOD", 4), +DeliveryObject(5, "410 S State St", "Salt Lake City", "UT", 84111, "EOD", 5), +DeliveryObject(6, "3060 Lester St", "West Valley City", "UT", 84119, "10:30 AM", 88, "Delayed on flight---will not arrive to depot until 9:05 am"), +DeliveryObject(7, "1330 2100 S", "Salt Lake City", "UT", 84106, "EOD", 8), +DeliveryObject(8, "300 State St", "Salt Lake City", "UT", 84103, "EOD", 9), +DeliveryObject(9, "300 State St", "Salt Lake City", "UT", 84103, "EOD", 2, "Wrong address listed"), +DeliveryObject(10, "600 E 900 South", "Salt Lake City", "UT", 84105, "EOD", 1), +DeliveryObject(11, "2600 Taylorsville Blvd", "Salt Lake City", "UT", 84118, "EOD", 1), +DeliveryObject(12, "3575 W Valley Central Station bus Loop", "West Valley City", "UT", 84119, "EOD", 1), +DeliveryObject(13, "2010 W 500 S", "Salt Lake City", "UT", 84104, "10:30 AM", 2), +DeliveryObject(14, "4300 S 1300 E", "Millcreek", "UT", 84117, "10:30 AM", 88, "Must be delivered with 15, 19"), +DeliveryObject(15, "4580 S 2300 E", "Holladay", "UT", 84117, "9:00 AM", 4), +DeliveryObject(16, "4580 S 2300 E", "Holladay", "UT", 84117, "10:30 AM", 88, "Must be delivered with 13, 19"), +DeliveryObject(17, "3148 S 1100 W", "Salt Lake City", "UT", 84119, "EOD", 2), +DeliveryObject(18, "1488 4800 S", "Salt Lake City", "UT", 84123, "EOD", 6, "Can only be on truck 2"), +DeliveryObject(19, "177 W Price Ave", "Salt Lake City", "UT", 84115, "EOD", 37), +DeliveryObject(20, "3595 Main St", "Salt Lake City", "UT", 84115, "10:30 AM", 37, "Must be delivered with 13, 15"), +DeliveryObject(21, "3595 Main St", "Salt Lake City", "UT", 84115, "EOD", 3), +DeliveryObject(22, "6351 South 900 East", "Murray", "UT", 84121, "EOD", 2), +DeliveryObject(23, "5100 South 2700 West", "Salt Lake City", "UT", 84118, "EOD", 5), +DeliveryObject(24, "5025 State St", "Murray", "UT", 84107, "EOD", 7), +DeliveryObject(25, "5383 South 900 East #104", "Salt Lake City", "UT", 84117, "10:30 AM", 7, "Delayed on flight---will not arrive to depot until 9:05 am"), +DeliveryObject(26, "5383 South 900 East #104", "Salt Lake City", "UT", 84117, "EOD", 25), +DeliveryObject(27, "1060 Dalton Ave S", "Salt Lake City", "UT", 84104, "EOD", 5), +DeliveryObject(28, "2835 Main St", "Salt Lake City", "UT", 84115, "EOD", 7, "Delayed on flight---will not arrive to depot until 9:05 am"), +DeliveryObject(29, "1330 2100 S", "Salt Lake City", "UT", 84106, "10:30 AM", 2), +DeliveryObject(30, "300 State St", "Salt Lake City", "UT", 84103, "10:30 AM", 1), +DeliveryObject(31, "3365 S 900 W", "Salt Lake City", "UT", 84119, "10:30 AM", 1), +DeliveryObject(32, "3365 S 900 W", "Salt Lake City", "UT", 84119, "EOD", 1, "Delayed on flight---will not arrive to depot until 9:05 am"), +DeliveryObject(33, "2530 S 500 E", "Salt Lake City", "UT", 84106, "EOD", 1), +DeliveryObject(34, "4580 S 2300 E", "Holladay", "UT", 84117, "10:30 AM", 2), +DeliveryObject(35, "1060 Dalton Ave S", "Salt Lake City", "UT", 84104, "EOD", 88), +DeliveryObject(36, "2300 Parkway Blvd", "West Valley City", "UT", 84119, "EOD", 88, "Can only be on truck 2"), +DeliveryObject(37, "410 S State St", "Salt Lake City", "UT", 84111, "10:30 AM", 2), +DeliveryObject(38, "410 S State St", "Salt Lake City", "UT", 84111, "EOD", 9, "Can only be on truck 2"), +DeliveryObject(39, "2010 W 500 S", "Salt Lake City", "UT", 84104, "EOD", 9), +DeliveryObject(40, "380 W 2880 S", "Salt Lake City", "UT", 84115, "10:30 AM", 45) +] \ No newline at end of file diff --git a/DeliveryObject.py b/DeliveryObject.py new file mode 100644 index 0000000..0d5e110 --- /dev/null +++ b/DeliveryObject.py @@ -0,0 +1,31 @@ +import datetime +from enum import Enum + +# This is just a DataType to store the package info +class DeliveryObject: + def __init__(self, id: str, address: str, city: str, state: str, zip: str, deadline: str, weight: float, specialNotes=""): + self.id: str = id + self.truckId = -1 + self.deliveryNumber = 0 + self.address: str = address + self.city: str = city + self.state: str = state + self.zipCode: str = zip + self.packageWeight: float = weight + self.notes = specialNotes + self.status: deliveryStatus = deliveryStatus.NO_STATUS + self.deadline: datetime.time = deadline + self.timeStart: datetime.time = None + self.timeEnd: datetime.time = None + if deadline != "EOD": + parts = deadline.replace(':', ' ').split() + self.deadline = datetime.time(int(parts[0]), int(parts[1])) + else: + self.deadline = datetime.time(23,59) + + +class deliveryStatus(Enum): + NO_STATUS = 0 + AT_THE_HUB = 1 + IN_ROUTE = 2 + DELIVERED = 3 \ No newline at end of file diff --git a/MyHashTable.py b/MyHashTable.py new file mode 100644 index 0000000..90f9899 --- /dev/null +++ b/MyHashTable.py @@ -0,0 +1,28 @@ +from MyLinkedList import DerekLinkedList + +class DerekHashTable: + def __init__(self) -> None: + self.backingStore = [DerekLinkedList() for _ in range(36)] # Create a linked list backing for the hash table + self.count = 0 + + def __iter__(self): + for linkedList in self.backingStore: # Look at each linked list + for data in linkedList: # Look at each item in the linked list + yield data # Return the data from each cell + + def _calcHash(self, key) -> int: # Use the built in hash function + return hash(key) % 36 # Modulo the size of the hash array + + def addItem(self, key, value): + i = self._calcHash(key) # Calculate a hash/modulo and put in the correct list + self.backingStore[i].append(key, value) + self.count += 1 + + def getItem(self, key): + i = self._calcHash(key) # Calculate the hash/modulo to get the data from the linked list + return self.backingStore[i].get(key) + + def remItem(self, key): + i = self._calcHash(key) # Calculate the hash/modulo to get the list + self.backingStore[i].remove(key) # Remove the item from the list + self.count -= 1 \ No newline at end of file diff --git a/MyLinkedList.py b/MyLinkedList.py new file mode 100644 index 0000000..32afedc --- /dev/null +++ b/MyLinkedList.py @@ -0,0 +1,59 @@ +# This is the node definition. Allowing each 'chain link' to hold data and point to the next +class DerekLinkedListNode: + def __init__(self, key, item) -> None: + self.key = key + self.data = item + self.next: DerekLinkedListNode | None = None + +# This is the real meat of the linked list +class DerekLinkedList: + # On init point to the first node and hold the total length + def __init__(self) -> None: + self.head: DerekLinkedListNode | None = None + self.len: int = 0 + + def __iter__(self): + current = self.head # Set the head of the linked list + while current: # if the value exists + yield current.data # return the data + current = current.next # set the next link + + # Appends an item to the end of the list + def append(self, key, item) -> None: + self.len += 1 # update the overall length + newNode = DerekLinkedListNode(key, item) # create the node to insert + if not self.head: # if the head doesnt exist + self.head = newNode # set the head as the node and return + return + cur = self.head # if the head exists set cur to the next node + while cur.next: # loop through the rest of the chain looking for the end + cur = cur.next + cur.next = newNode # add the new node to the end + + # Removes an item at the index + def remove(self, key) -> None: + lastNode = self.head # previous node + curNode = self.head # current node + while key != curNode.key: # get to the node we are curious about + lastNode = curNode + if curNode.next == None: + return None + curNode = curNode.next + if curNode == self.head: + self.head = curNode.next + else: + lastNode.next = curNode.next # pull the current node out of the linked list + self.len -= 1 # remove the length by 1 + + # returns the data at the index + def get(self, key): + if self.head: # if the head exists + cur: DerekLinkedListNode | None = self.head # set the current searching for at the head + while key != cur.key: # go through the chain until we hit the index were looking for + if cur.next == None: + return + cur = cur.next + if isinstance(cur, DerekLinkedListNode): # since we made it here the data were on is a LinkedListNode + return cur.data # return the value out of the node + else: + return None \ No newline at end of file diff --git a/Screenshots/.DS_Store b/Screenshots/.DS_Store new file mode 100644 index 0000000..87426b8 Binary files /dev/null and b/Screenshots/.DS_Store differ diff --git a/Truck.py b/Truck.py new file mode 100644 index 0000000..6b017f4 --- /dev/null +++ b/Truck.py @@ -0,0 +1,91 @@ +import sys +import AssignmentData +import datetime +from MyHashTable import DerekHashTable +from DeliveryObject import DeliveryObject +from DeliveryObject import deliveryStatus + +class Truck: + def __init__(self, truckId: str): + self.id = truckId # This trucks ID + self.packages: list[DeliveryObject] = [] # The packages on the truck + self.hasLoad: bool = False # Status of if the truck was loaded + self.mileage = 0.0 # Mileage counter + self.deliveryCounter = 0 # Keep track of the delivery counter + self.current_location = "HUB" # Current location of the truck + self.speed = 18 # Set from the project instructions + self.startTime: datetime.time | None = None # The trucks start time + + def addPkg(self, pkg: DeliveryObject): # Adds a package to the truck + if len(self.packages) < 16: # Check if we can fit another package + pkg.timeStart = self.getTimeOfDay() # Set the start time for the pkg + pkg.truckId = self.id # Set the truck ID for the end menu + pkg.status = deliveryStatus.IN_ROUTE # The package is on the truck + self.packages.append(pkg) # Add the package + else: + print("to many packages loaded on the truck") # If there are too many packages + sys.exit() # Crash the program + + def getDriveTime(self) -> int: # Usefull outside this class + hourPercent = self.mileage / self.speed # Calculate the hour percentage + rounededMin = int(60 * hourPercent) # Average that into minutes + return rounededMin + + def getTimeOfDay(self) -> datetime.time: # Get the time of day using offsets + truckStartMins = (self.startTime.hour * 60) + self.startTime.minute # Get the minutes past midnight when the truck left + offsetMinutes = truckStartMins + self.getDriveTime() # Add on the number of minutes the truck as been driving + pkgHours = (offsetMinutes // 60) % 24 # pull hours back out + pkgMins = offsetMinutes % 60 # pull minutes back out + return datetime.time(pkgHours, pkgMins) # return the time of day + + def deliverPkgs(self): # Delivers any packeges that match the current location + for i in range(len(self.packages) - 1, -1, -1): # Loop backwards so removals dont index shift + if self.packages[i].address in self.current_location: # Check if the package is at the correct address + self.packages[i].status = deliveryStatus.DELIVERED # Mark as delivered + self.packages[i].timeEnd = self.getTimeOfDay() # Mark the delivered time + self.deliveryCounter += 1 # Incriment the delivery counter + self.packages[i].deliveryNumber = self.deliveryCounter # Mark the package number + self.packages.remove(self.packages[i]) # Remove the package from the truck + + def getMatrixIndex(self, address_string: str) -> int: # Helper function to get x or y offset in the distance table + for i, full_name in enumerate(AssignmentData.xyNames2): # Loop through the names in the list + if address_string in full_name: # Check if the name matches + return i # Return the index + for i, full_name in enumerate(AssignmentData.xyNames): # Loop through the names in the list + if address_string in full_name: # Check if the name matches + return i # Return the index + print(f"Address {address_string} is not in Full List") # Report items not found in the list + return 0 # Default to Hub if not found + + def driveNextClosest(self, priority: DerekHashTable) -> int: # The bread and butter of the algorithm + # deal with priority + priorityPkgs = [] # set the packages we currently want to look for + for cur in self.packages: # loop thorugh looking for priority matches + if priority.getItem(cur.id): + priorityPkgs.append(cur) # if a priority matches add it to the list + if not priorityPkgs: # if no priority matches just use the normal + priorityPkgs = self.packages + + # get the next closest + shortest_dist = float('inf') # Start with a very long distance to work backwards from + next_pkg = None # Keep track of the closest pkg + + current_idx = self.getMatrixIndex(self.current_location) # Get the trucks xyIndex + for pkg in priorityPkgs: # Loop through the packages + dest_idx = self.getMatrixIndex(pkg.address) # Get the package xyIndex + + row = max(current_idx, dest_idx) # Becuase the distance table is a triange + col = min(current_idx, dest_idx) # Keep on the buttom left side + dist = AssignmentData.distanceChart[row][col] # Get the distance from the grid + + if dist < shortest_dist: # If the distance is shorter than the one found + shortest_dist = dist # Update the distance and package + next_pkg = pkg + + # drive + self.mileage += shortest_dist # Add this shortest distance to the truck + self.current_location = next_pkg.address # update the location + self.deliverPkgs() # deliver packages at this location + if len(self.packages) == 0: # if there is no packages + self.hasLoad = False # Set the load to empty + return len(self.packages) # return packages left to deliver \ No newline at end of file diff --git a/UI.py b/UI.py new file mode 100644 index 0000000..2297336 --- /dev/null +++ b/UI.py @@ -0,0 +1,147 @@ +import datetime + +class DataUI: + + # Filter for selections that allow + def selectionChooser(self, fullList): + print("Please make a selection") + print(" [1]: Show all ") + print(" [2]: Show per truck") + print(" [3]: Show package") + SubMenuTreeSelection = input("") + selection = [] + if "1" in SubMenuTreeSelection: + selection = fullList + if "2" in SubMenuTreeSelection: + print("Choose a truck ID [1,2,3]") + choice = input("") + for pkg in fullList: + if choice in pkg.truckId: + selection.append(pkg) + if "3" in SubMenuTreeSelection: + print("Choose a package ID [0..39]") + choice = input("") + for pkg in fullList: + if choice == str(pkg.id): + selection.append(pkg) + return selection + + # Shows information about trucks + def Trucks(self, truck1, truck2, truck3): + trucks = [truck1, truck2, truck3] + print("") + for i,v in enumerate(trucks): + startTime = "Pending" + endTime = "Pending" + if v.startTime != None: + startTime = v.startTime.strftime('%H:%M') + if v.packages.count == 0: + endTime = v.getTimeOfDay().strftime('%H:%M') + + print(f"Truck [{v.id}] | " + f"Mileage: {v.mileage:.2f} | " + f"Departure: {startTime} | " + f"Arrival: {endTime} | " + f"DriveTime: {v.getDriveTime()} mintes") + print("") + + # Shows information about packages + def Packages(self, package_hash): + selection = self.selectionChooser(package_hash) + print("") + for i,v in enumerate(selection): + print(f"[{v.id:2}] | " + f"[DeliveryNumber]: {v.deliveryNumber:2} | " + f"[onTruck]: {v.truckId:2} | " + f"[Address]: {v.address[:20]:20} | " + f"[Status]: {v.status:10} | " + f"[Note]: {v.notes:10}") + print("") + + # Shows information about delivery times and deadlines + def DeliveryTime(self, package_hash): + selection = self.selectionChooser(package_hash) + print("") + for i,v in enumerate(selection): + deadline = v.deadline + metDeadline = "False" + if isinstance(v.deadline, datetime.time): + deadline = v.deadline.strftime('%H:%M') + if v.timeStart != None and v.timeEnd != None: + if v.timeEnd <= v.deadline: + metDeadline = "True" + if v.deadline == datetime.time(23,59): + deadline = "EOD" + startTime = "Pending" + if v.timeStart != None: + startTime = v.timeStart.strftime('%H:%M') + endTime = "Pending" + if v.timeEnd != None: + endTime = v.timeEnd.strftime('%H:%M') + print(f"[{v.id:2}] | " + f"[StartTime]: {startTime:8} | " + f"[DelivedTime]: {endTime:8} | " + f"[Deadline]: {deadline:8} | " + f"[MetDeadline]: {metDeadline:5} | " + f"[Note]: {v.notes:10}") + print("") + + # Main prompt for the UI + def MainSelectionPrompt(self, truck1, truck2, truck3, package_hash): + while True: + print("Please make a selection") + print(" [1]: Show stats for trucks") + print(" [2]: Show stats for packages") + print(" [3]: Check delivery times") + print(" [4]: Total Mileage for trucks") + print(" [5]: Cancel") + MenuTreeSelection = input("") + if "1" in MenuTreeSelection: + self.Trucks(truck1, truck2, truck3) + elif "2" in MenuTreeSelection: + self.Packages(package_hash) + elif "3" in MenuTreeSelection: + self.DeliveryTime(package_hash) + elif "4" in MenuTreeSelection: + totalDistance = truck1.mileage + truck2.mileage + truck3.mileage + print(f"\nTotal Mileage for all trucks: {totalDistance:.2f} miles\n") + elif "5" in MenuTreeSelection: + break + + # Prompt for pausing the simulation and opening the menu for stats at specific times + def TimePrompt(self, currentTime: datetime.time, truck1, truck2, truck3, package_hash) -> datetime.time: + while True: + print(" [1]: Pause the simulation at a point in time") + print(" [2]: Run the simulation to the end") + print(" [3]: View stats for this moment in time") + + selectionInput = input("") + if "1" in selectionInput: + while True: + print("Please input a time in format of [hh:mm] or type 'back' to return to menu") + timeInput = input("") + + if timeInput.lower() == 'back': + break + + try: + time = timeInput.split(":") + hour = int(time[0]) + minute = int(time[1]) + + chosenTime = datetime.time(hour, minute) + if chosenTime > currentTime: + print(f"Simulation set to pause at {hour:02}:{minute:02}") + return chosenTime + else: + print(f"Error: Time must be later than {currentTime.strftime('%H:%M')}.") + + except ValueError: + print(f"Invalid format '{timeInput}'. Use HH:MM (e.g., 10:30).") + elif "2" in selectionInput: + print("Running simulation until the end of the day.") + return datetime.time(23, 59) + elif "3" in selectionInput: + self.MainSelectionPrompt(truck1, truck2, truck3, package_hash) + else: + print("Invalid selection. Please choose 1, 2, or 3.") \ No newline at end of file diff --git a/WGU_Truck_Delivery.docx b/WGU_Truck_Delivery.docx new file mode 100644 index 0000000..a5783d8 Binary files /dev/null and b/WGU_Truck_Delivery.docx differ diff --git a/main.py b/main.py new file mode 100755 index 0000000..bc323f6 --- /dev/null +++ b/main.py @@ -0,0 +1,128 @@ +# Derek Holloway 010396173 + +import datetime +import AssignmentData +import UI +from MyHashTable import DerekHashTable +from Truck import Truck +from DeliveryObject import deliveryStatus + +def main(): + + UserUI = UI.DataUI() # Load in the UI library + globalTime = datetime.time(8, 0) + + package_hash = DerekHashTable() # Init the hash table + for pkg in AssignmentData.packages: # Load the packages into the hash table + pkg.status = deliveryStatus.AT_THE_HUB + package_hash.addItem(pkg.id, pkg) + + truck1 = Truck("1") # Init truck 1 starting at 8:00 + truck1.startTime = datetime.time(8, 0) + + truck2 = Truck("2") # Init truck 2 starting at 9:15 -> Delayed for package 6 + truck2.startTime = datetime.time(9, 5) + + truck3 = Truck("3") # Init truck 3 starting as soon as the fastest truck returned + truck3.startTime = None + + pauseTime = UserUI.TimePrompt(globalTime, truck1, truck2, truck3, package_hash) # start the first time prompt + + noRequirements = [1,2,4,5,7,8,10,11,12,17,21,22,23,24,26,27,29,30,31,33,34,35,37,39,40] + truck2Only = [3,18,36,38] + delayed = [6,9,25,28,32] + sameTruck = [13,14,15,16,19,20] + + # Package requirements + priority = [1,6,13,14,15,16,21,25,29,30,31,34,37,40] + priority_hash = DerekHashTable() # Load the priority packages into a hash for quick lookup + for cur in priority: + priority_hash.addItem(cur, cur) + + # Sort the known packages + t1_load = sameTruck # Start with the packages that requrie the same truck + t2_load = truck2Only + delayed # add in the truck 2 only and the delays + t3_load = [] # create an empty list + + # Sort the Unknown packages + for i, v in enumerate(noRequirements): # enumerate through all the packages not predefined + if priority_hash.getItem(v): # if the item is a priority item + if len(t1_load) < 16: # load priorities on truck 1 first as it leaves earlier + t1_load.append(v) + elif len(t2_load) < 16: # fallback to truck 2 + t2_load.append(v) + else: # fallback again to truck 3 + t3_load.append(v) + else: + if len(t3_load) < 16: # non priority pkgs default to truck 3 as it leaves last + t3_load.append(v) + elif len(t2_load) < 16: # fall back to truck 2 + t2_load.append(v) + elif len(t1_load) < 16: # fallback again to truck 1 + t1_load.append(v) + + # Run the simulation until all the packages have been delivered + debounce1 = True + debounce2 = True + debounce3 = True + truck3LoadDebounce = True + while True: + + # update global time + if globalTime >= datetime.time(10, 20): # If the time is correct for update + pkg9 = package_hash.getItem(9) # Handle the Package #9 address correction before Truck 3 starts + pkg9.address = "410 S State St" + + # load packages for truck 1 + if globalTime >= truck1.startTime and debounce1: # If its time to laod the packages, run once + debounce1 = False # set debounce for only one run + truck1.hasLoad = True # Set the truck load variable + for pkg_id in t1_load: # Load the packages in truck 1 + truck1.addPkg(package_hash.getItem(pkg_id)) + + # load packages for truck 2 + if globalTime >= truck2.startTime and debounce2: # If its time to laod the packages, run once + debounce2 = False # set debounce for only one run + truck2.hasLoad = True # Set the truck load variable + for pkg_id in t2_load: # Load the packages in truck 2 + truck2.addPkg(package_hash.getItem(pkg_id)) + + # load packages for truck 3 + if globalTime >= truck3.startTime and debounce3: # If its time to laod the packages, run once + debounce3 = False # set debounce for only one run + truck2.hasLoad = True # Set the truck load variable + for pkg_id in t2_load: # Load the packages in truck 2 + truck2.addPkg(package_hash.getItem(pkg_id)) + + # deliver packages for truck 1 + if truck1.hasLoad and globalTime >= truck1.getTimeOfDay(): # if there is more packages and the time has passed + packagesLeft = truck1.driveNextClosest(priority_hash) # deliver the next set of packages + if packagesLeft == 0 and truck3LoadDebounce: # if all the packages are delivered + truck3LoadDebounce = False # set the debouce + truck3.startTime = truck1.getTimeOfDay() # set truck3 start time + + # deliver packages for truck 2 + if truck2.hasLoad and globalTime >= truck2.getTimeOfDay(): # if there is more packages and the time has passed + packagesLeft = truck2.driveNextClosest(priority_hash) # deliver the next set of packages + if packagesLeft == 0 and truck3LoadDebounce: # if all the packages are delivered + truck3LoadDebounce = False # set the debouce + truck3.startTime = truck1.getTimeOfDay() # set truck3 start time + + # deliver packages for truck 3 + if truck3.hasLoad and globalTime >= truck3.getTimeOfDay(): # if there is more packages and the time has passed + truck3.driveNextClosest(priority_hash) # deliver the next set of packages + + # Calculate new global time + totalMins = (globalTime.hour * 60) + globalTime.minute + 5 # Get total mins and add 30 + offsetHour = (totalMins // 60) % 24 # pull hours back out + offsetMins = totalMins % 60 # pull minutes back out + globalTime = datetime.time(offsetHour, offsetMins) # return the time of day + + # if we are done delivering + if (truck3.hasLoad == False and truck2.hasLoad == False and truck1.hasLoad == False): + break + + UserUI.MainSelectionPrompt(truck1, truck2, truck3, package_hash) # Display the command prompt Menus + +if __name__ == "__main__": + main() \ No newline at end of file