wgu_repo/Data Structures and Algorithms II — C950/Task 2 - Python files + Screenshots/main.py

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)