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

161 lines
6.3 KiB
Python

# Student ID: 012498637
# Student Name: Zakaria Benmoulay
# Course: C950 - Data Structures and Algorithms II
from datetime import datetime, timedelta
SPEED_MPH = 18 # all trucks travel at 18 mph
HUB_ADDRESS = "Salt Lake City, UT 84107" # WGU hub — matches the distance table
class Truck:
# Let's initialize the truck with assigned packages and starting conditions.
def __init__(self, truck_id, packages, depart_time, package_table,
address_list, distance_matrix):
self.truck_id = truck_id
self.package_ids = list(packages) # copy so original list is not modified
self.depart_time = depart_time
self.package_table = package_table
self.address_list = address_list
self.distance_matrix = distance_matrix
# Simulation state — updated as deliver_all() runs
self.current_address = HUB_ADDRESS # truck starts at the hub
self.current_time = depart_time # clock starts at departure time
self.total_miles = 0.0 # accumulates mileage during delivery
self.route = [] # list of (package_id, address, arrival_time)
# Distance and time helpers
def _distance(self, addr_a, addr_b):
def find_index(addr):
addr_clean = addr.strip().lower()
# Pass 1: exact match
for i, a in enumerate(self.address_list):
if a.strip().lower() == addr_clean:
return i
# Pass 2: substring match (handles abbreviations and extra words)
for i, a in enumerate(self.address_list):
if addr_clean in a.strip().lower() or a.strip().lower() in addr_clean:
return i
# Pass 3: match on the first two tokens (street number + street name)
tokens = addr_clean.split()
if tokens:
prefix = " ".join(tokens[:2])
for i, a in enumerate(self.address_list):
if a.strip().lower().startswith(prefix):
return i
raise ValueError(f"Address not found in distance table: '{addr}'")
i = find_index(addr_a)
j = find_index(addr_b)
return self.distance_matrix[i][j]
def _travel_time(self, miles):
hours = miles / SPEED_MPH
return timedelta(hours=hours)
# Core nearest-neighbor delivery algorithm
def deliver_all(self, return_to_hub=False):
# Step 1 — Mark all packages on this truck as En Route at departure time
for pid in self.package_ids:
pkg = self.package_table.lookup(pid)
if pkg:
pkg.mark_en_route(self.depart_time, self.truck_id)
# Work from a copy so the original package_ids list is preserved
remaining = list(self.package_ids)
# Step 2 — Nearest-neighbor delivery loop
while remaining:
# Inner scan: find the package with the shortest distance
# from the truck's current location
nearest_pid = None
nearest_dist = float("inf") # start with infinity so any real distance wins
for pid in remaining:
pkg = self.package_table.lookup(pid)
if pkg is None:
continue
# Get the correct address for this package at this time
# (handles Package 9's address correction at 10:20 AM)
address = self._resolve_address(pid, self.current_time)
dist = self._distance(self.current_address, address)
# Update nearest if this package is closer than the current best
if dist < nearest_dist:
nearest_dist = dist
nearest_pid = pid
if nearest_pid is None:
break # safety check — should not happen with valid data
# Step 2b — Drive to the nearest package's address
pkg = self.package_table.lookup(nearest_pid)
dest_address = self._resolve_address(nearest_pid, self.current_time)
travel = self._travel_time(nearest_dist)
self.current_time += travel # advance the simulated clock
self.total_miles += nearest_dist # accumulate mileage
self.current_address = dest_address # move truck to new location
# Step 2c — Mark the package as delivered at the current time
pkg.mark_delivered(self.current_time)
self.route.append((nearest_pid, dest_address, self.current_time))
# Step 2d — Remove from remaining list
remaining.remove(nearest_pid)
# Step 3 — Return to hub if needed (for driver reassignment to Truck 3)
if return_to_hub:
dist_home = self._distance(self.current_address, HUB_ADDRESS)
self.total_miles += dist_home
self.current_time += self._travel_time(dist_home)
self.current_address = HUB_ADDRESS
# Package 9 address correction
def _resolve_address(self, package_id, current_time):
pkg = self.package_table.lookup(package_id)
if package_id == 9:
# Define the correction time as 10:20 AM on the simulation date
correction_time = self.depart_time.replace(
hour=10, minute=20, second=0, microsecond=0
)
if current_time >= correction_time:
return "410 S State St" # corrected address after 10:20 AM
else:
return pkg.address # wrong address before 10:20 AM
return pkg.address # all other packages return their address unchanged
# Route summary display
def print_route(self):
print(f"\n=== Truck {self.truck_id} Route ===")
print(f"Departed hub : {self.depart_time.strftime('%I:%M %p')}")
for pid, addr, arrival in self.route:
pkg = self.package_table.lookup(pid)
deadline = pkg.deadline if pkg else "?"
print(
f" Pkg {pid:>2} -> {addr:<40} "
f"Arrived: {arrival.strftime('%I:%M %p')} "
f"Deadline: {deadline}"
)
print(f"Total miles : {self.total_miles:.2f}")
print(f"Finished at : {self.current_time.strftime('%I:%M %p')}")