239 lines
8.4 KiB
Python
239 lines
8.4 KiB
Python
# Student ID: 012498637
|
|
# Student Name: Zakaria Benmoulay
|
|
# Course: C950 - Data Structures and Algorithms II
|
|
# Algorithm : Nearest-Neighbor (greedy self-adjusting)
|
|
# Data Structure: Custom Chaining Hash Table
|
|
|
|
# Let's import the required libraries and modules
|
|
import os
|
|
from datetime import datetime
|
|
from data_loader import load_packages, load_distances
|
|
from truck import Truck
|
|
|
|
|
|
|
|
# Let's define file paths for package and distance data (relative to this script)
|
|
|
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
PACKAGES_CSV = os.path.join(BASE_DIR, "packages.csv")
|
|
DISTANCES_CSV = os.path.join(BASE_DIR, "distances.csv")
|
|
|
|
|
|
# Let's load all necessary data for the program
|
|
def initialize():
|
|
package_table = load_packages(PACKAGES_CSV)
|
|
address_list, distance_matrix = load_distances(DISTANCES_CSV)
|
|
return package_table, address_list, distance_matrix
|
|
|
|
|
|
|
|
|
|
# Let's assign packages to trucks based on delivery constraints
|
|
# Manual truck loading (satisfies all constraints)
|
|
def load_trucks(package_table, address_list, distance_matrix):
|
|
"""Assign packages to trucks and run the delivery algorithm."""
|
|
|
|
# Let's set the start of the delivery day (8:00 AM)
|
|
start_of_day = datetime(2024, 1, 1, 8, 0, 0) # 8:00 AM
|
|
|
|
# ---- Truck 1 ----
|
|
# Let's load priority packages and grouped deliveries
|
|
truck1_packages = [
|
|
1, 13, 14, 15, 16, 19, 20, 29, 30, 31, 34, 37, 40,
|
|
2, 4, 5, 7, 10,
|
|
]
|
|
|
|
# ---- Truck 2 ----
|
|
# Let's load truck-specific and delayed packages
|
|
truck2_packages = [
|
|
3, 18, 36, 38,
|
|
6, 28, 32,
|
|
9,
|
|
8, 11, 12, 17, 21, 23, 24,
|
|
]
|
|
|
|
# ---- Truck 3 ----
|
|
# Let's assign any remaining packages to Truck 3
|
|
all_assigned = set(truck1_packages + truck2_packages)
|
|
all_packages = [pid for pid, _ in package_table.get_all()]
|
|
truck3_packages = [pid for pid in all_packages if pid not in all_assigned]
|
|
|
|
# Let's initialize Truck 1 (departs at 8:00 AM)
|
|
truck1 = Truck(
|
|
truck_id=1,
|
|
packages=truck1_packages,
|
|
depart_time=start_of_day,
|
|
package_table=package_table,
|
|
address_list=address_list,
|
|
distance_matrix=distance_matrix,
|
|
)
|
|
|
|
# Let's delay Truck 2 departure until 9:05 AM for late packages
|
|
truck2_depart = datetime(2024, 1, 1, 9, 5, 0)
|
|
truck2 = Truck(
|
|
truck_id=2,
|
|
packages=truck2_packages,
|
|
depart_time=truck2_depart,
|
|
package_table=package_table,
|
|
address_list=address_list,
|
|
distance_matrix=distance_matrix,
|
|
)
|
|
|
|
# Let's run deliveries for Truck 1 and Truck 2
|
|
truck1.deliver_all(return_to_hub=True)
|
|
truck2.deliver_all(return_to_hub=True)
|
|
|
|
# Let's assign Truck 3 to depart when Truck 1 returns
|
|
truck3_depart = truck1.current_time
|
|
truck3 = Truck(
|
|
truck_id=3,
|
|
packages=truck3_packages,
|
|
depart_time=truck3_depart,
|
|
package_table=package_table,
|
|
address_list=address_list,
|
|
distance_matrix=distance_matrix,
|
|
)
|
|
|
|
# Let's complete deliveries for Truck 3
|
|
truck3.deliver_all(return_to_hub=True)
|
|
|
|
return truck1, truck2, truck3
|
|
|
|
|
|
# CLI INTERFACE
|
|
# Let's display the status of all packages at a given time
|
|
def print_all_status(package_table, query_time):
|
|
"""Print the delivery status of all 40 packages at a given time."""
|
|
print(f"\n{'='*75}")
|
|
print(f" Package Status at {query_time.strftime('%I:%M %p')}")
|
|
print(f"{'='*75}")
|
|
print(f"{'ID':<4} {'Address':<38} {'Deadline':<10} {'Truck':<6} {'Status':<12} {'Delivered'}")
|
|
print(f"{'-'*75}")
|
|
|
|
# Sort packages by ID so output is easy to read
|
|
all_packages = sorted(package_table.get_all(), key=lambda x: x[0])
|
|
for pid, pkg in all_packages:
|
|
# status_at() compares query_time to departure and delivery times
|
|
status = pkg.status_at(query_time)
|
|
delivered_str = (
|
|
pkg.delivery_time.strftime("%I:%M %p")
|
|
if status == "Delivered" and pkg.delivery_time
|
|
else "—"
|
|
)
|
|
truck_str = str(pkg.truck_id) if pkg.truck_id else "—"
|
|
print(
|
|
f"{pid:<4} {pkg.address:<38} {pkg.deadline:<10} "
|
|
f"{truck_str:<6} {status:<12} {delivered_str}"
|
|
)
|
|
|
|
|
|
# Let's display the status of a single package
|
|
def print_single_status(package_table, pkg_id, query_time):
|
|
"""Print full details for a single package at a given time."""
|
|
# Use the hash table lookup function to retrieve the package by ID
|
|
pkg = package_table.lookup(pkg_id)
|
|
if pkg is None:
|
|
print(f"Package {pkg_id} not found.")
|
|
return
|
|
|
|
status = pkg.status_at(query_time)
|
|
delivered_str = (
|
|
pkg.delivery_time.strftime("%I:%M %p")
|
|
if status == "Delivered" and pkg.delivery_time
|
|
else "Not yet delivered"
|
|
)
|
|
|
|
# Display all data components associated with this package ID
|
|
print(f"\n{'='*50}")
|
|
print(f" Package {pkg.package_id} — Status at {query_time.strftime('%I:%M %p')}")
|
|
print(f"{'='*50}")
|
|
print(f" Address : {pkg.address}, {pkg.city}, {pkg.state} {pkg.zip_code}")
|
|
print(f" Deadline : {pkg.deadline}")
|
|
print(f" Weight : {pkg.weight} kg")
|
|
print(f" Truck : {pkg.truck_id if pkg.truck_id else '—'}")
|
|
print(f" Notes : {pkg.notes if pkg.notes else 'None'}")
|
|
print(f" Status : {status}")
|
|
print(f" Delivered: {delivered_str}")
|
|
|
|
|
|
# Let's convert user input into a valid time format
|
|
def parse_time(time_str):
|
|
"""Parse a user-entered time string into a datetime object."""
|
|
reference_date = datetime(2024, 1, 1)
|
|
for fmt in ["%I:%M %p", "%H:%M", "%I:%M%p"]:
|
|
try:
|
|
t = datetime.strptime(time_str.strip(), fmt)
|
|
return reference_date.replace(hour=t.hour, minute=t.minute, second=0)
|
|
except ValueError:
|
|
continue
|
|
return None # Return None if no format matched
|
|
|
|
|
|
# Let's create the main user interface loop
|
|
def main(package_table, truck1, truck2, truck3):
|
|
# Calculate and display total combined mileage at startup
|
|
total_miles = truck1.total_miles + truck2.total_miles + truck3.total_miles
|
|
|
|
print("\n" + "="*60)
|
|
print(" WGUPS Package Routing Program")
|
|
print("="*60)
|
|
print(f" Truck 1 mileage : {truck1.total_miles:.2f} miles")
|
|
print(f" Truck 2 mileage : {truck2.total_miles:.2f} miles")
|
|
print(f" Truck 3 mileage : {truck3.total_miles:.2f} miles")
|
|
print(f" Total mileage : {total_miles:.2f} miles")
|
|
print("="*60)
|
|
|
|
# Menu loop — continues until the user enters 4 to exit
|
|
while True:
|
|
print("\nOptions:")
|
|
print(" 1 - View status of ALL packages at a specific time")
|
|
print(" 2 - View status of a SINGLE package at a specific time")
|
|
print(" 3 - Print all truck routes")
|
|
print(" 4 - Exit")
|
|
choice = input("\nEnter choice (1/2/3/4): ").strip()
|
|
|
|
if choice == "1":
|
|
# Prompt for a time and display all 40 package statuses
|
|
time_str = input("Enter time (e.g. 9:00 AM or 13:00): ")
|
|
query_time = parse_time(time_str)
|
|
if query_time is None:
|
|
print("Invalid time format. Try '9:00 AM' or '09:00'.")
|
|
else:
|
|
print_all_status(package_table, query_time)
|
|
|
|
elif choice == "2":
|
|
# Prompt for a package ID and time, then display that package
|
|
try:
|
|
pkg_id = int(input("Enter package ID (1-40): "))
|
|
except ValueError:
|
|
print("Invalid package ID.")
|
|
continue
|
|
time_str = input("Enter time (e.g. 9:00 AM or 13:00): ")
|
|
query_time = parse_time(time_str)
|
|
if query_time is None:
|
|
print("Invalid time format.")
|
|
else:
|
|
print_single_status(package_table, pkg_id, query_time)
|
|
|
|
elif choice == "3":
|
|
# Print the full ordered route for each truck with delivery times
|
|
truck1.print_route()
|
|
truck2.print_route()
|
|
truck3.print_route()
|
|
total = truck1.total_miles + truck2.total_miles + truck3.total_miles
|
|
print(f"\nCombined total: {total:.2f} miles")
|
|
|
|
elif choice == "4":
|
|
print("Thank you for using WGUPS Routing Program, Goodbye.")
|
|
break
|
|
|
|
else:
|
|
print("Invalid choice. Please enter 1, 2, 3, or 4.")
|
|
|
|
|
|
# Let's start the program execution
|
|
if __name__ == "__main__":
|
|
package_table, address_list, distance_matrix = initialize()
|
|
truck1, truck2, truck3 = load_trucks(package_table, address_list, distance_matrix)
|
|
main(package_table, truck1, truck2, truck3)
|