# 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)