adding Data Structures and Algorithms II C950

This commit is contained in:
Zakaria 2026-03-27 15:37:56 -04:00
parent 3aaba75f39
commit 21898dcbfd
57 changed files with 1460 additions and 0 deletions

BIN
.DS_Store vendored

Binary file not shown.

BIN
D682_AIOFCS/.DS_Store vendored

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 3.14" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
</module>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.14" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.14" project-jdk-type="Python SDK" />
</project>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/Task 2 - Python files.iml" filepath="$PROJECT_DIR$/.idea/Task 2 - Python files.iml" />
</modules>
</component>
</project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
</component>
</project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 557 KiB

View File

@ -0,0 +1,96 @@
# Student ID: 012498637
# Student Name: Zakaria Benmoulay
# Course: C950 - Data Structures and Algorithms II
import csv
from package import Package
from hash_table import ChainingHashTable
def load_packages(filepath):
# Let's read packages.csv and load all package records into a hash table.
table = ChainingHashTable()
with open(filepath, newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
# Build a Package object from each CSV row
pkg = Package(
package_id=row["PackageID"],
address=row["Address"],
city=row["City"],
state=row["State"],
zip_code=row["Zip"],
deadline=row["Deadline"],
weight=row["Weight"],
notes=row["Notes"],
)
# Insert into the hash table using package ID as the key
table.insert(pkg.package_id, pkg)
# Let's return the hash table keyed by package ID
return table
def load_distances(filepath):
# Let's read distances.csv and build a symmetric 2-D distance matrix.
address_list = []
raw_rows = []
with open(filepath, newline="", encoding="utf-8") as f:
reader = csv.reader(f)
# First row contains the column headers (location name + address)
header = next(reader)
# Extract the short street address from each header cell.
# Header cells may contain a full location name followed by the
# street address separated by whitespace; we take the last part.
for cell in header[1:]: # skip the first "Address" label column
parts = [p.strip() for p in cell.replace("\n", " ").split(" ") if p.strip()]
address_list.append(parts[-1] if parts else cell.strip())
# Read the remaining rows — each row is one location's distances
for row in reader:
raw_rows.append(row[1:]) # drop the first column (location label)
m = len(address_list)
# Create an m×m matrix filled with 0.0
matrix = [[0.0] * m for _ in range(m)]
# Fill in the matrix - the CSV only has the lower triangle
for i, row in enumerate(raw_rows):
for j, cell in enumerate(row):
if cell.strip() == "" or cell is None:
continue
dist = float(cell)
matrix[i][j] = dist
matrix[j][i] = dist
return address_list, matrix
def get_distance(address_list, distance_matrix, addr_a, addr_b):
# Let's find the index of an address using flexible matching.
def find_index(addr):
addr_clean = addr.strip().lower()
# Pass 1: exact match (fastest, covers most cases)
for i, a in enumerate(address_list):
if a.strip().lower() == addr_clean:
return i
# Pass 2: substring match (handles minor formatting differences)
for i, a in enumerate(address_list):
if addr_clean in a.strip().lower() or a.strip().lower() in addr_clean:
return i
# Raise an error if the address cannot be found.
raise ValueError(f"Address not found in distance table: '{addr}'")
i = find_index(addr_a)
j = find_index(addr_b)
return distance_matrix[i][j]

View File

@ -0,0 +1,28 @@
Address,"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
"Western Governors University 4001 South 700 East, Salt Lake City, UT 84107",0,,,,,,,,,,,,,,,,,,,,,,,,,,
International Peace Gardens 1060 Dalton Ave S,7.2,0,,,,,,,,,,,,,,,,,,,,,,,,,
Sugar House Park 1330 2100 S,3.8,7.1,0,,,,,,,,,,,,,,,,,,,,,,,,
Taylorsville-Bennion Heritage City Gov Off 1488 4800 S,11,6.4,9.2,0,,,,,,,,,,,,,,,,,,,,,,,
Salt Lake City Division of Health Services 177 W Price Ave,2.2,6,4.4,5.6,0,,,,,,,,,,,,,,,,,,,,,,
South Salt Lake Public Works 195 W Oakland Ave,3.5,4.8,2.8,6.9,1.9,0,,,,,,,,,,,,,,,,,,,,,
Salt Lake City Streets and Sanitation 2010 W 500 S,10.9,1.6,8.6,8.6,7.9,6.3,0,,,,,,,,,,,,,,,,,,,,
Deker Lake 2300 Parkway Blvd,8.6,2.8,6.3,4,5.1,4.3,4,0,,,,,,,,,,,,,,,,,,,
Salt Lake City Ottinger Hall 233 Canyon Rd,7.6,4.8,5.3,11.1,7.5,4.5,4.2,7.7,0,,,,,,,,,,,,,,,,,,
Columbus Library 2530 S 500 E,2.8,6.3,1.6,7.3,2.6,1.5,8,9.3,4.8,0,,,,,,,,,,,,,,,,,
Taylorsville City Hall 2600 Taylorsville Blvd,6.4,7.3,10.4,1,6.5,8.7,8.6,4.6,11.9,9.4,0,,,,,,,,,,,,,,,,
South Salt Lake Police 2835 Main St,3.2,5.3,3,6.4,1.5,0.8,6.9,4.8,4.7,1.1,7.3,0,,,,,,,,,,,,,,,
Council Hall 300 State St,7.6,4.8,5.3,11.1,7.5,4.5,4.2,7.7,0.6,5.1,12,4.7,0,,,,,,,,,,,,,,
Redwood Park 3060 Lester St,5.2,3,6.5,3.9,3.2,3.9,4.2,1.6,7.6,4.6,4.9,3.5,7.3,0,,,,,,,,,,,,,
Salt Lake County Mental Health 3148 S 1100 W,4.4,4.6,5.6,4.3,2.4,3,8,3.3,7.8,3.7,5.2,2.6,7.8,1.3,0,,,,,,,,,,,,
Salt Lake County/United Police Dept 3365 S 900 W,3.66112816434,4.5,5.8,4.4,2.7,3.8,5.8,3.4,6.6,4,5.4,2.9,6.6,1.5,0.6,0,,,,,,,,,,,
West Valley Prosecutor 3575 W Valley Central Sta bus Loop,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,6.4,5.6,0,,,,,,,,,,
Housing Auth. of Salt Lake County 3595 Main St,2,6,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,,,,,,,,,
Utah DMV Administrative Office 380 W 2880 S,3.6,5,3.6,6,1.7,1.1,6.6,4.6,5.4,1.8,6.9,1,5.4,3,2.2,1.7,6.1,1.6,0,,,,,,,,
Third District Juvenile Court 410 S State St,6.5,4.8,4.3,10.6,6.5,3.5,3.2,6.7,1,4.1,11.5,3.7,1,6.9,6.8,6.4,7.2,4.9,4.4,0,,,,,,,
Cottonwood Regional Softball Complex 4300 S 1300 E,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,4.6,7.5,0,,,,,,
Holiday City Office 4580 S 2300 E,3.4,10.9,5,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,5,6.6,9.3,2,0,,,,,
Murray City Museum 5025 State St,2.4,8.3,6.1,4.7,2.5,4.2,10,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,,,,
Valley Regional Softball Complex 5100 South 2700 West,6.4,6.9,9.7,0.6,6,9,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,,,
City Center of Rock Springs 5383 South 900 East #104,2.4,10,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,5.6,8.5,2.8,3.4,1.7,5.4,0,,
Rice Terrace Pavilion Park 600 E 900 South,5,4.4,2.8,10.1,5.4,3.5,5.1,6.2,2.8,3.2,11,3.7,2.8,6.4,6.5,5.7,6.2,5.1,4.3,1.8,6,7.9,6.8,10.6,7,0,
Wheeler Historic Farm 6351 South 900 East,3.6,13,7.4,10.1,5.5,7.2,14.2,10.7,14.1,6,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,0
1 Address 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
2 Western Governors University 4001 South 700 East, Salt Lake City, UT 84107 0
3 International Peace Gardens 1060 Dalton Ave S 7.2 0
4 Sugar House Park 1330 2100 S 3.8 7.1 0
5 Taylorsville-Bennion Heritage City Gov Off 1488 4800 S 11 6.4 9.2 0
6 Salt Lake City Division of Health Services 177 W Price Ave 2.2 6 4.4 5.6 0
7 South Salt Lake Public Works 195 W Oakland Ave 3.5 4.8 2.8 6.9 1.9 0
8 Salt Lake City Streets and Sanitation 2010 W 500 S 10.9 1.6 8.6 8.6 7.9 6.3 0
9 Deker Lake 2300 Parkway Blvd 8.6 2.8 6.3 4 5.1 4.3 4 0
10 Salt Lake City Ottinger Hall 233 Canyon Rd 7.6 4.8 5.3 11.1 7.5 4.5 4.2 7.7 0
11 Columbus Library 2530 S 500 E 2.8 6.3 1.6 7.3 2.6 1.5 8 9.3 4.8 0
12 Taylorsville City Hall 2600 Taylorsville Blvd 6.4 7.3 10.4 1 6.5 8.7 8.6 4.6 11.9 9.4 0
13 South Salt Lake Police 2835 Main St 3.2 5.3 3 6.4 1.5 0.8 6.9 4.8 4.7 1.1 7.3 0
14 Council Hall 300 State St 7.6 4.8 5.3 11.1 7.5 4.5 4.2 7.7 0.6 5.1 12 4.7 0
15 Redwood Park 3060 Lester St 5.2 3 6.5 3.9 3.2 3.9 4.2 1.6 7.6 4.6 4.9 3.5 7.3 0
16 Salt Lake County Mental Health 3148 S 1100 W 4.4 4.6 5.6 4.3 2.4 3 8 3.3 7.8 3.7 5.2 2.6 7.8 1.3 0
17 Salt Lake County/United Police Dept 3365 S 900 W 3.66112816434 4.5 5.8 4.4 2.7 3.8 5.8 3.4 6.6 4 5.4 2.9 6.6 1.5 0.6 0
18 West Valley Prosecutor 3575 W Valley Central Sta bus Loop 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 6.4 5.6 0
19 Housing Auth. of Salt Lake County 3595 Main St 2 6 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
20 Utah DMV Administrative Office 380 W 2880 S 3.6 5 3.6 6 1.7 1.1 6.6 4.6 5.4 1.8 6.9 1 5.4 3 2.2 1.7 6.1 1.6 0
21 Third District Juvenile Court 410 S State St 6.5 4.8 4.3 10.6 6.5 3.5 3.2 6.7 1 4.1 11.5 3.7 1 6.9 6.8 6.4 7.2 4.9 4.4 0
22 Cottonwood Regional Softball Complex 4300 S 1300 E 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 4.6 7.5 0
23 Holiday City Office 4580 S 2300 E 3.4 10.9 5 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 5 6.6 9.3 2 0
24 Murray City Museum 5025 State St 2.4 8.3 6.1 4.7 2.5 4.2 10 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
25 Valley Regional Softball Complex 5100 South 2700 West 6.4 6.9 9.7 0.6 6 9 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
26 City Center of Rock Springs 5383 South 900 East #104 2.4 10 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 5.6 8.5 2.8 3.4 1.7 5.4 0
27 Rice Terrace Pavilion Park 600 E 900 South 5 4.4 2.8 10.1 5.4 3.5 5.1 6.2 2.8 3.2 11 3.7 2.8 6.4 6.5 5.7 6.2 5.1 4.3 1.8 6 7.9 6.8 10.6 7 0
28 Wheeler Historic Farm 6351 South 900 East 3.6 13 7.4 10.1 5.5 7.2 14.2 10.7 14.1 6 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 0

View File

@ -0,0 +1,66 @@
# Student ID: 012498637
# Student Name: Zakaria Benmoulay
# Course: C950 - Data Structures and Algorithms II
class ChainingHashTable:
# We are going to assigns all buckets with an empty list.
def __init__(self, initial_capacity=40):
# Let's initialize the hash table with empty bucket list entries.
self.table = []
for i in range(initial_capacity):
self.table.append([])
# This func inserts a new item into the hash table.
def insert(self, key, item):
# Get the bucket list where this item will go.
bucket = hash(key) % len(self.table)
bucket_list = self.table[bucket]
# Let's check if the key already exists and update it if found.
for kv in bucket_list:
if kv[0] == key:
kv[1] = item
return True
# otherwise, we insert the item to the end of list.
key_value = [key, item]
bucket_list.append(key_value)
return True
# This func returns the item if found, or None if not found.
def lookup(self, key):
# Let's find the correct bucket for this key.
bucket = hash(key) % len(self.table)
bucket_list = self.table[bucket]
# Let's search through the bucket for the matching key.
for kv in bucket_list:
if kv[0] == key:
return kv[1]
return None
# This func removes an item with matching key from the hash table.
def remove(self, key):
# Let's find the bucket where this key should exist.
bucket = hash(key) % len(self.table)
bucket_list = self.table[bucket]
# Let's search for and remove the matching key-value pair.
for kv in bucket_list:
if kv[0] == key:
bucket_list.remove([kv[0], kv[1]])
return print('Package ', key, 'successfully deleted.')
# Let's return None if the key is not found.
return None
# This func returns a list of all (key, value) tuples stored in the hash table.
# Let's use this to iterate through all packages when needed.
def get_all(self):
result = []
for bucket_list in self.table:
for kv in bucket_list:
result.append((kv[0], kv[1]))
return result

View File

@ -0,0 +1,238 @@
# 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)

View File

@ -0,0 +1,79 @@
# Student ID: 012498637
# Student Name: Zakaria Benmoulay
# Course: C950 - Data Structures and Algorithms II
class Package:
def __init__(
self,
package_id,
address,
city,
state,
zip_code,
deadline,
weight,
notes="",
):
# Let's store the core delivery data from the Package File
self.package_id = int(package_id)
self.address = address.strip()
self.city = city.strip()
self.state = state.strip()
self.zip_code = str(zip_code).strip()
self.deadline = deadline.strip()
self.weight = weight
self.notes = notes.strip()
# Let's initialize the status tracking fields for the simulation
self.status = "At Hub"
self.departure_time = None
self.delivery_time = None
self.truck_id = None
# STATUS UPDATES
def mark_en_route(self, departure_time, truck_id):
# Called when a truck departs the hub carrying this package.
self.status = "En Route"
self.departure_time = departure_time
self.truck_id = truck_id
def mark_delivered(self, delivery_time):
# Called when the truck arrives at this package's delivery address.
self.status = "Delivered"
self.delivery_time = delivery_time
# TIME-BASED STATUS QUERY
def status_at(self, query_time):
# Return the status this package had at a specific point in time.
# Package is still at the hub if no truck has departed with it yet,
# or if the query time is before the truck's departure.
if self.departure_time is None or query_time < self.departure_time:
return "At Hub"
# Package is en route if the truck has left but delivery hasn't happened yet
if self.delivery_time is None or query_time < self.delivery_time:
return "En Route"
# Otherwise the package was already delivered by query_time
return "Delivered"
# DISPLAY
def __repr__(self):
return (
f"Package({self.package_id:>2}, "
f"{self.address:<38} "
f"Deadline: {self.deadline:<8} "
f"Wt: {self.weight:<4} "
f"Status: {self.status})"
)

View File

@ -0,0 +1,41 @@
PackageID,Address,City,State,Zip,Deadline,Weight,Notes
1,195 W Oakland Ave,Salt Lake City,UT,84115,10:30:00,21,
2,2530 S 500 E,Salt Lake City,UT,84106,EOD,44,
3,233 Canyon Rd,Salt Lake City,UT,84103,EOD,2,Can only be on truck 2
4,380 W 2880 S,Salt Lake City,UT,84115,EOD,4,
5,410 S State St,Salt Lake City,UT,84111,EOD,5,
6,3060 Lester St,West Valley City,UT,84119,10:30:00,88,Delayed on flight---will not arrive to depot until 9:05 am
7,1330 2100 S,Salt Lake City,UT,84106,EOD,8,
8,300 State St,Salt Lake City,UT,84103,EOD,9,
9,300 State St,Salt Lake City,UT,84103,EOD,2,Wrong address listed
10,600 E 900 South,Salt Lake City,UT,84105,EOD,1,
11,2600 Taylorsville Blvd,Salt Lake City,UT,84118,EOD,1,
12,3575 W Valley Central Station bus Loop,West Valley City,UT,84119,EOD,1,
13,2010 W 500 S,Salt Lake City,UT,84104,10:30:00,2,
14,4300 S 1300 E,Millcreek,UT,84117,10:30:00,88,"Must be delivered with 15, 19"
15,4580 S 2300 E,Holladay,UT,84117,09:00:00,4,
16,4580 S 2300 E,Holladay,UT,84117,10:30:00,88,"Must be delivered with 13, 19"
17,3148 S 1100 W,Salt Lake City,UT,84119,EOD,2,
18,1488 4800 S,Salt Lake City,UT,84123,EOD,6,Can only be on truck 2
19,177 W Price Ave,Salt Lake City,UT,84115,EOD,37,
20,3595 Main St,Salt Lake City,UT,84115,10:30:00,37,"Must be delivered with 13, 15"
21,3595 Main St,Salt Lake City,UT,84115,EOD,3,
22,6351 South 900 East,Murray,UT,84121,EOD,2,
23,5100 South 2700 West,Salt Lake City,UT,84118,EOD,5,
24,5025 State St,Murray,UT,84107,EOD,7,
25,5383 South 900 East #104,Salt Lake City,UT,84117,10:30:00,7,Delayed on flight---will not arrive to depot until 9:05 am
26,5383 South 900 East #104,Salt Lake City,UT,84117,EOD,25,
27,1060 Dalton Ave S,Salt Lake City,UT,84104,EOD,5,
28,2835 Main St,Salt Lake City,UT,84115,EOD,7,Delayed on flight---will not arrive to depot until 9:05 am
29,1330 2100 S,Salt Lake City,UT,84106,10:30:00,2,
30,300 State St,Salt Lake City,UT,84103,10:30:00,1,
31,3365 S 900 W,Salt Lake City,UT,84119,10:30:00,1,
32,3365 S 900 W,Salt Lake City,UT,84119,EOD,1,Delayed on flight---will not arrive to depot until 9:05 am
33,2530 S 500 E,Salt Lake City,UT,84106,EOD,1,
34,4580 S 2300 E,Holladay,UT,84117,10:30:00,2,
35,1060 Dalton Ave S,Salt Lake City,UT,84104,EOD,88,
36,2300 Parkway Blvd,West Valley City,UT,84119,EOD,88,Can only be on truck 2
37,410 S State St,Salt Lake City,UT,84111,10:30:00,2,
38,410 S State St,Salt Lake City,UT,84111,EOD,9,Can only be on truck 2
39,2010 W 500 S,Salt Lake City,UT,84104,EOD,9,
40,380 W 2880 S,Salt Lake City,UT,84115,10:30:00,45,
1 PackageID Address City State Zip Deadline Weight Notes
2 1 195 W Oakland Ave Salt Lake City UT 84115 10:30:00 21
3 2 2530 S 500 E Salt Lake City UT 84106 EOD 44
4 3 233 Canyon Rd Salt Lake City UT 84103 EOD 2 Can only be on truck 2
5 4 380 W 2880 S Salt Lake City UT 84115 EOD 4
6 5 410 S State St Salt Lake City UT 84111 EOD 5
7 6 3060 Lester St West Valley City UT 84119 10:30:00 88 Delayed on flight---will not arrive to depot until 9:05 am
8 7 1330 2100 S Salt Lake City UT 84106 EOD 8
9 8 300 State St Salt Lake City UT 84103 EOD 9
10 9 300 State St Salt Lake City UT 84103 EOD 2 Wrong address listed
11 10 600 E 900 South Salt Lake City UT 84105 EOD 1
12 11 2600 Taylorsville Blvd Salt Lake City UT 84118 EOD 1
13 12 3575 W Valley Central Station bus Loop West Valley City UT 84119 EOD 1
14 13 2010 W 500 S Salt Lake City UT 84104 10:30:00 2
15 14 4300 S 1300 E Millcreek UT 84117 10:30:00 88 Must be delivered with 15, 19
16 15 4580 S 2300 E Holladay UT 84117 09:00:00 4
17 16 4580 S 2300 E Holladay UT 84117 10:30:00 88 Must be delivered with 13, 19
18 17 3148 S 1100 W Salt Lake City UT 84119 EOD 2
19 18 1488 4800 S Salt Lake City UT 84123 EOD 6 Can only be on truck 2
20 19 177 W Price Ave Salt Lake City UT 84115 EOD 37
21 20 3595 Main St Salt Lake City UT 84115 10:30:00 37 Must be delivered with 13, 15
22 21 3595 Main St Salt Lake City UT 84115 EOD 3
23 22 6351 South 900 East Murray UT 84121 EOD 2
24 23 5100 South 2700 West Salt Lake City UT 84118 EOD 5
25 24 5025 State St Murray UT 84107 EOD 7
26 25 5383 South 900 East #104 Salt Lake City UT 84117 10:30:00 7 Delayed on flight---will not arrive to depot until 9:05 am
27 26 5383 South 900 East #104 Salt Lake City UT 84117 EOD 25
28 27 1060 Dalton Ave S Salt Lake City UT 84104 EOD 5
29 28 2835 Main St Salt Lake City UT 84115 EOD 7 Delayed on flight---will not arrive to depot until 9:05 am
30 29 1330 2100 S Salt Lake City UT 84106 10:30:00 2
31 30 300 State St Salt Lake City UT 84103 10:30:00 1
32 31 3365 S 900 W Salt Lake City UT 84119 10:30:00 1
33 32 3365 S 900 W Salt Lake City UT 84119 EOD 1 Delayed on flight---will not arrive to depot until 9:05 am
34 33 2530 S 500 E Salt Lake City UT 84106 EOD 1
35 34 4580 S 2300 E Holladay UT 84117 10:30:00 2
36 35 1060 Dalton Ave S Salt Lake City UT 84104 EOD 88
37 36 2300 Parkway Blvd West Valley City UT 84119 EOD 88 Can only be on truck 2
38 37 410 S State St Salt Lake City UT 84111 10:30:00 2
39 38 410 S State St Salt Lake City UT 84111 EOD 9 Can only be on truck 2
40 39 2010 W 500 S Salt Lake City UT 84104 EOD 9
41 40 380 W 2880 S Salt Lake City UT 84115 10:30:00 45

View File

@ -0,0 +1,160 @@
# 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')}")

View File

@ -0,0 +1,96 @@
# Student ID: 012498637
# Student Name: Zakaria Benmoulay
# Course: C950 - Data Structures and Algorithms II
import csv
from package import Package
from hash_table import ChainingHashTable
def load_packages(filepath):
# Let's read packages.csv and load all package records into a hash table.
table = ChainingHashTable()
with open(filepath, newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
# Build a Package object from each CSV row
pkg = Package(
package_id=row["PackageID"],
address=row["Address"],
city=row["City"],
state=row["State"],
zip_code=row["Zip"],
deadline=row["Deadline"],
weight=row["Weight"],
notes=row["Notes"],
)
# Insert into the hash table using package ID as the key
table.insert(pkg.package_id, pkg)
# Let's return the hash table keyed by package ID
return table
def load_distances(filepath):
# Let's read distances.csv and build a symmetric 2-D distance matrix.
address_list = []
raw_rows = []
with open(filepath, newline="", encoding="utf-8") as f:
reader = csv.reader(f)
# First row contains the column headers (location name + address)
header = next(reader)
# Extract the short street address from each header cell.
# Header cells may contain a full location name followed by the
# street address separated by whitespace; we take the last part.
for cell in header[1:]: # skip the first "Address" label column
parts = [p.strip() for p in cell.replace("\n", " ").split(" ") if p.strip()]
address_list.append(parts[-1] if parts else cell.strip())
# Read the remaining rows — each row is one location's distances
for row in reader:
raw_rows.append(row[1:]) # drop the first column (location label)
m = len(address_list)
# Create an m×m matrix filled with 0.0
matrix = [[0.0] * m for _ in range(m)]
# Fill in the matrix - the CSV only has the lower triangle
for i, row in enumerate(raw_rows):
for j, cell in enumerate(row):
if cell.strip() == "" or cell is None:
continue
dist = float(cell)
matrix[i][j] = dist
matrix[j][i] = dist
return address_list, matrix
def get_distance(address_list, distance_matrix, addr_a, addr_b):
# Let's find the index of an address using flexible matching.
def find_index(addr):
addr_clean = addr.strip().lower()
# Pass 1: exact match (fastest, covers most cases)
for i, a in enumerate(address_list):
if a.strip().lower() == addr_clean:
return i
# Pass 2: substring match (handles minor formatting differences)
for i, a in enumerate(address_list):
if addr_clean in a.strip().lower() or a.strip().lower() in addr_clean:
return i
# Raise an error if the address cannot be found.
raise ValueError(f"Address not found in distance table: '{addr}'")
i = find_index(addr_a)
j = find_index(addr_b)
return distance_matrix[i][j]

View File

@ -0,0 +1,28 @@
Address,"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
"Western Governors University 4001 South 700 East, Salt Lake City, UT 84107",0,,,,,,,,,,,,,,,,,,,,,,,,,,
International Peace Gardens 1060 Dalton Ave S,7.2,0,,,,,,,,,,,,,,,,,,,,,,,,,
Sugar House Park 1330 2100 S,3.8,7.1,0,,,,,,,,,,,,,,,,,,,,,,,,
Taylorsville-Bennion Heritage City Gov Off 1488 4800 S,11,6.4,9.2,0,,,,,,,,,,,,,,,,,,,,,,,
Salt Lake City Division of Health Services 177 W Price Ave,2.2,6,4.4,5.6,0,,,,,,,,,,,,,,,,,,,,,,
South Salt Lake Public Works 195 W Oakland Ave,3.5,4.8,2.8,6.9,1.9,0,,,,,,,,,,,,,,,,,,,,,
Salt Lake City Streets and Sanitation 2010 W 500 S,10.9,1.6,8.6,8.6,7.9,6.3,0,,,,,,,,,,,,,,,,,,,,
Deker Lake 2300 Parkway Blvd,8.6,2.8,6.3,4,5.1,4.3,4,0,,,,,,,,,,,,,,,,,,,
Salt Lake City Ottinger Hall 233 Canyon Rd,7.6,4.8,5.3,11.1,7.5,4.5,4.2,7.7,0,,,,,,,,,,,,,,,,,,
Columbus Library 2530 S 500 E,2.8,6.3,1.6,7.3,2.6,1.5,8,9.3,4.8,0,,,,,,,,,,,,,,,,,
Taylorsville City Hall 2600 Taylorsville Blvd,6.4,7.3,10.4,1,6.5,8.7,8.6,4.6,11.9,9.4,0,,,,,,,,,,,,,,,,
South Salt Lake Police 2835 Main St,3.2,5.3,3,6.4,1.5,0.8,6.9,4.8,4.7,1.1,7.3,0,,,,,,,,,,,,,,,
Council Hall 300 State St,7.6,4.8,5.3,11.1,7.5,4.5,4.2,7.7,0.6,5.1,12,4.7,0,,,,,,,,,,,,,,
Redwood Park 3060 Lester St,5.2,3,6.5,3.9,3.2,3.9,4.2,1.6,7.6,4.6,4.9,3.5,7.3,0,,,,,,,,,,,,,
Salt Lake County Mental Health 3148 S 1100 W,4.4,4.6,5.6,4.3,2.4,3,8,3.3,7.8,3.7,5.2,2.6,7.8,1.3,0,,,,,,,,,,,,
Salt Lake County/United Police Dept 3365 S 900 W,3.66112816434,4.5,5.8,4.4,2.7,3.8,5.8,3.4,6.6,4,5.4,2.9,6.6,1.5,0.6,0,,,,,,,,,,,
West Valley Prosecutor 3575 W Valley Central Sta bus Loop,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,6.4,5.6,0,,,,,,,,,,
Housing Auth. of Salt Lake County 3595 Main St,2,6,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,,,,,,,,,
Utah DMV Administrative Office 380 W 2880 S,3.6,5,3.6,6,1.7,1.1,6.6,4.6,5.4,1.8,6.9,1,5.4,3,2.2,1.7,6.1,1.6,0,,,,,,,,
Third District Juvenile Court 410 S State St,6.5,4.8,4.3,10.6,6.5,3.5,3.2,6.7,1,4.1,11.5,3.7,1,6.9,6.8,6.4,7.2,4.9,4.4,0,,,,,,,
Cottonwood Regional Softball Complex 4300 S 1300 E,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,4.6,7.5,0,,,,,,
Holiday City Office 4580 S 2300 E,3.4,10.9,5,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,5,6.6,9.3,2,0,,,,,
Murray City Museum 5025 State St,2.4,8.3,6.1,4.7,2.5,4.2,10,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,,,,
Valley Regional Softball Complex 5100 South 2700 West,6.4,6.9,9.7,0.6,6,9,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,,,
City Center of Rock Springs 5383 South 900 East #104,2.4,10,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,5.6,8.5,2.8,3.4,1.7,5.4,0,,
Rice Terrace Pavilion Park 600 E 900 South,5,4.4,2.8,10.1,5.4,3.5,5.1,6.2,2.8,3.2,11,3.7,2.8,6.4,6.5,5.7,6.2,5.1,4.3,1.8,6,7.9,6.8,10.6,7,0,
Wheeler Historic Farm 6351 South 900 East,3.6,13,7.4,10.1,5.5,7.2,14.2,10.7,14.1,6,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,0
1 Address 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
2 Western Governors University 4001 South 700 East, Salt Lake City, UT 84107 0
3 International Peace Gardens 1060 Dalton Ave S 7.2 0
4 Sugar House Park 1330 2100 S 3.8 7.1 0
5 Taylorsville-Bennion Heritage City Gov Off 1488 4800 S 11 6.4 9.2 0
6 Salt Lake City Division of Health Services 177 W Price Ave 2.2 6 4.4 5.6 0
7 South Salt Lake Public Works 195 W Oakland Ave 3.5 4.8 2.8 6.9 1.9 0
8 Salt Lake City Streets and Sanitation 2010 W 500 S 10.9 1.6 8.6 8.6 7.9 6.3 0
9 Deker Lake 2300 Parkway Blvd 8.6 2.8 6.3 4 5.1 4.3 4 0
10 Salt Lake City Ottinger Hall 233 Canyon Rd 7.6 4.8 5.3 11.1 7.5 4.5 4.2 7.7 0
11 Columbus Library 2530 S 500 E 2.8 6.3 1.6 7.3 2.6 1.5 8 9.3 4.8 0
12 Taylorsville City Hall 2600 Taylorsville Blvd 6.4 7.3 10.4 1 6.5 8.7 8.6 4.6 11.9 9.4 0
13 South Salt Lake Police 2835 Main St 3.2 5.3 3 6.4 1.5 0.8 6.9 4.8 4.7 1.1 7.3 0
14 Council Hall 300 State St 7.6 4.8 5.3 11.1 7.5 4.5 4.2 7.7 0.6 5.1 12 4.7 0
15 Redwood Park 3060 Lester St 5.2 3 6.5 3.9 3.2 3.9 4.2 1.6 7.6 4.6 4.9 3.5 7.3 0
16 Salt Lake County Mental Health 3148 S 1100 W 4.4 4.6 5.6 4.3 2.4 3 8 3.3 7.8 3.7 5.2 2.6 7.8 1.3 0
17 Salt Lake County/United Police Dept 3365 S 900 W 3.66112816434 4.5 5.8 4.4 2.7 3.8 5.8 3.4 6.6 4 5.4 2.9 6.6 1.5 0.6 0
18 West Valley Prosecutor 3575 W Valley Central Sta bus Loop 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 6.4 5.6 0
19 Housing Auth. of Salt Lake County 3595 Main St 2 6 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
20 Utah DMV Administrative Office 380 W 2880 S 3.6 5 3.6 6 1.7 1.1 6.6 4.6 5.4 1.8 6.9 1 5.4 3 2.2 1.7 6.1 1.6 0
21 Third District Juvenile Court 410 S State St 6.5 4.8 4.3 10.6 6.5 3.5 3.2 6.7 1 4.1 11.5 3.7 1 6.9 6.8 6.4 7.2 4.9 4.4 0
22 Cottonwood Regional Softball Complex 4300 S 1300 E 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 4.6 7.5 0
23 Holiday City Office 4580 S 2300 E 3.4 10.9 5 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 5 6.6 9.3 2 0
24 Murray City Museum 5025 State St 2.4 8.3 6.1 4.7 2.5 4.2 10 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
25 Valley Regional Softball Complex 5100 South 2700 West 6.4 6.9 9.7 0.6 6 9 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
26 City Center of Rock Springs 5383 South 900 East #104 2.4 10 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 5.6 8.5 2.8 3.4 1.7 5.4 0
27 Rice Terrace Pavilion Park 600 E 900 South 5 4.4 2.8 10.1 5.4 3.5 5.1 6.2 2.8 3.2 11 3.7 2.8 6.4 6.5 5.7 6.2 5.1 4.3 1.8 6 7.9 6.8 10.6 7 0
28 Wheeler Historic Farm 6351 South 900 East 3.6 13 7.4 10.1 5.5 7.2 14.2 10.7 14.1 6 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 0

View File

@ -0,0 +1,66 @@
# Student ID: 012498637
# Student Name: Zakaria Benmoulay
# Course: C950 - Data Structures and Algorithms II
class ChainingHashTable:
# We are going to assigns all buckets with an empty list.
def __init__(self, initial_capacity=40):
# Let's initialize the hash table with empty bucket list entries.
self.table = []
for i in range(initial_capacity):
self.table.append([])
# This func inserts a new item into the hash table.
def insert(self, key, item):
# Get the bucket list where this item will go.
bucket = hash(key) % len(self.table)
bucket_list = self.table[bucket]
# Let's check if the key already exists and update it if found.
for kv in bucket_list:
if kv[0] == key:
kv[1] = item
return True
# otherwise, we insert the item to the end of list.
key_value = [key, item]
bucket_list.append(key_value)
return True
# This func returns the item if found, or None if not found.
def lookup(self, key):
# Let's find the correct bucket for this key.
bucket = hash(key) % len(self.table)
bucket_list = self.table[bucket]
# Let's search through the bucket for the matching key.
for kv in bucket_list:
if kv[0] == key:
return kv[1]
return None
# This func removes an item with matching key from the hash table.
def remove(self, key):
# Let's find the bucket where this key should exist.
bucket = hash(key) % len(self.table)
bucket_list = self.table[bucket]
# Let's search for and remove the matching key-value pair.
for kv in bucket_list:
if kv[0] == key:
bucket_list.remove([kv[0], kv[1]])
return print('Package ', key, 'successfully deleted.')
# Let's return None if the key is not found.
return None
# This func returns a list of all (key, value) tuples stored in the hash table.
# Let's use this to iterate through all packages when needed.
def get_all(self):
result = []
for bucket_list in self.table:
for kv in bucket_list:
result.append((kv[0], kv[1]))
return result

View File

@ -0,0 +1,238 @@
# 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)

View File

@ -0,0 +1,79 @@
# Student ID: 012498637
# Student Name: Zakaria Benmoulay
# Course: C950 - Data Structures and Algorithms II
class Package:
def __init__(
self,
package_id,
address,
city,
state,
zip_code,
deadline,
weight,
notes="",
):
# Let's store the core delivery data from the Package File
self.package_id = int(package_id)
self.address = address.strip()
self.city = city.strip()
self.state = state.strip()
self.zip_code = str(zip_code).strip()
self.deadline = deadline.strip()
self.weight = weight
self.notes = notes.strip()
# Let's initialize the status tracking fields for the simulation
self.status = "At Hub"
self.departure_time = None
self.delivery_time = None
self.truck_id = None
# STATUS UPDATES
def mark_en_route(self, departure_time, truck_id):
# Called when a truck departs the hub carrying this package.
self.status = "En Route"
self.departure_time = departure_time
self.truck_id = truck_id
def mark_delivered(self, delivery_time):
# Called when the truck arrives at this package's delivery address.
self.status = "Delivered"
self.delivery_time = delivery_time
# TIME-BASED STATUS QUERY
def status_at(self, query_time):
# Return the status this package had at a specific point in time.
# Package is still at the hub if no truck has departed with it yet,
# or if the query time is before the truck's departure.
if self.departure_time is None or query_time < self.departure_time:
return "At Hub"
# Package is en route if the truck has left but delivery hasn't happened yet
if self.delivery_time is None or query_time < self.delivery_time:
return "En Route"
# Otherwise the package was already delivered by query_time
return "Delivered"
# DISPLAY
def __repr__(self):
return (
f"Package({self.package_id:>2}, "
f"{self.address:<38} "
f"Deadline: {self.deadline:<8} "
f"Wt: {self.weight:<4} "
f"Status: {self.status})"
)

View File

@ -0,0 +1,41 @@
PackageID,Address,City,State,Zip,Deadline,Weight,Notes
1,195 W Oakland Ave,Salt Lake City,UT,84115,10:30:00,21,
2,2530 S 500 E,Salt Lake City,UT,84106,EOD,44,
3,233 Canyon Rd,Salt Lake City,UT,84103,EOD,2,Can only be on truck 2
4,380 W 2880 S,Salt Lake City,UT,84115,EOD,4,
5,410 S State St,Salt Lake City,UT,84111,EOD,5,
6,3060 Lester St,West Valley City,UT,84119,10:30:00,88,Delayed on flight---will not arrive to depot until 9:05 am
7,1330 2100 S,Salt Lake City,UT,84106,EOD,8,
8,300 State St,Salt Lake City,UT,84103,EOD,9,
9,300 State St,Salt Lake City,UT,84103,EOD,2,Wrong address listed
10,600 E 900 South,Salt Lake City,UT,84105,EOD,1,
11,2600 Taylorsville Blvd,Salt Lake City,UT,84118,EOD,1,
12,3575 W Valley Central Station bus Loop,West Valley City,UT,84119,EOD,1,
13,2010 W 500 S,Salt Lake City,UT,84104,10:30:00,2,
14,4300 S 1300 E,Millcreek,UT,84117,10:30:00,88,"Must be delivered with 15, 19"
15,4580 S 2300 E,Holladay,UT,84117,09:00:00,4,
16,4580 S 2300 E,Holladay,UT,84117,10:30:00,88,"Must be delivered with 13, 19"
17,3148 S 1100 W,Salt Lake City,UT,84119,EOD,2,
18,1488 4800 S,Salt Lake City,UT,84123,EOD,6,Can only be on truck 2
19,177 W Price Ave,Salt Lake City,UT,84115,EOD,37,
20,3595 Main St,Salt Lake City,UT,84115,10:30:00,37,"Must be delivered with 13, 15"
21,3595 Main St,Salt Lake City,UT,84115,EOD,3,
22,6351 South 900 East,Murray,UT,84121,EOD,2,
23,5100 South 2700 West,Salt Lake City,UT,84118,EOD,5,
24,5025 State St,Murray,UT,84107,EOD,7,
25,5383 South 900 East #104,Salt Lake City,UT,84117,10:30:00,7,Delayed on flight---will not arrive to depot until 9:05 am
26,5383 South 900 East #104,Salt Lake City,UT,84117,EOD,25,
27,1060 Dalton Ave S,Salt Lake City,UT,84104,EOD,5,
28,2835 Main St,Salt Lake City,UT,84115,EOD,7,Delayed on flight---will not arrive to depot until 9:05 am
29,1330 2100 S,Salt Lake City,UT,84106,10:30:00,2,
30,300 State St,Salt Lake City,UT,84103,10:30:00,1,
31,3365 S 900 W,Salt Lake City,UT,84119,10:30:00,1,
32,3365 S 900 W,Salt Lake City,UT,84119,EOD,1,Delayed on flight---will not arrive to depot until 9:05 am
33,2530 S 500 E,Salt Lake City,UT,84106,EOD,1,
34,4580 S 2300 E,Holladay,UT,84117,10:30:00,2,
35,1060 Dalton Ave S,Salt Lake City,UT,84104,EOD,88,
36,2300 Parkway Blvd,West Valley City,UT,84119,EOD,88,Can only be on truck 2
37,410 S State St,Salt Lake City,UT,84111,10:30:00,2,
38,410 S State St,Salt Lake City,UT,84111,EOD,9,Can only be on truck 2
39,2010 W 500 S,Salt Lake City,UT,84104,EOD,9,
40,380 W 2880 S,Salt Lake City,UT,84115,10:30:00,45,
1 PackageID Address City State Zip Deadline Weight Notes
2 1 195 W Oakland Ave Salt Lake City UT 84115 10:30:00 21
3 2 2530 S 500 E Salt Lake City UT 84106 EOD 44
4 3 233 Canyon Rd Salt Lake City UT 84103 EOD 2 Can only be on truck 2
5 4 380 W 2880 S Salt Lake City UT 84115 EOD 4
6 5 410 S State St Salt Lake City UT 84111 EOD 5
7 6 3060 Lester St West Valley City UT 84119 10:30:00 88 Delayed on flight---will not arrive to depot until 9:05 am
8 7 1330 2100 S Salt Lake City UT 84106 EOD 8
9 8 300 State St Salt Lake City UT 84103 EOD 9
10 9 300 State St Salt Lake City UT 84103 EOD 2 Wrong address listed
11 10 600 E 900 South Salt Lake City UT 84105 EOD 1
12 11 2600 Taylorsville Blvd Salt Lake City UT 84118 EOD 1
13 12 3575 W Valley Central Station bus Loop West Valley City UT 84119 EOD 1
14 13 2010 W 500 S Salt Lake City UT 84104 10:30:00 2
15 14 4300 S 1300 E Millcreek UT 84117 10:30:00 88 Must be delivered with 15, 19
16 15 4580 S 2300 E Holladay UT 84117 09:00:00 4
17 16 4580 S 2300 E Holladay UT 84117 10:30:00 88 Must be delivered with 13, 19
18 17 3148 S 1100 W Salt Lake City UT 84119 EOD 2
19 18 1488 4800 S Salt Lake City UT 84123 EOD 6 Can only be on truck 2
20 19 177 W Price Ave Salt Lake City UT 84115 EOD 37
21 20 3595 Main St Salt Lake City UT 84115 10:30:00 37 Must be delivered with 13, 15
22 21 3595 Main St Salt Lake City UT 84115 EOD 3
23 22 6351 South 900 East Murray UT 84121 EOD 2
24 23 5100 South 2700 West Salt Lake City UT 84118 EOD 5
25 24 5025 State St Murray UT 84107 EOD 7
26 25 5383 South 900 East #104 Salt Lake City UT 84117 10:30:00 7 Delayed on flight---will not arrive to depot until 9:05 am
27 26 5383 South 900 East #104 Salt Lake City UT 84117 EOD 25
28 27 1060 Dalton Ave S Salt Lake City UT 84104 EOD 5
29 28 2835 Main St Salt Lake City UT 84115 EOD 7 Delayed on flight---will not arrive to depot until 9:05 am
30 29 1330 2100 S Salt Lake City UT 84106 10:30:00 2
31 30 300 State St Salt Lake City UT 84103 10:30:00 1
32 31 3365 S 900 W Salt Lake City UT 84119 10:30:00 1
33 32 3365 S 900 W Salt Lake City UT 84119 EOD 1 Delayed on flight---will not arrive to depot until 9:05 am
34 33 2530 S 500 E Salt Lake City UT 84106 EOD 1
35 34 4580 S 2300 E Holladay UT 84117 10:30:00 2
36 35 1060 Dalton Ave S Salt Lake City UT 84104 EOD 88
37 36 2300 Parkway Blvd West Valley City UT 84119 EOD 88 Can only be on truck 2
38 37 410 S State St Salt Lake City UT 84111 10:30:00 2
39 38 410 S State St Salt Lake City UT 84111 EOD 9 Can only be on truck 2
40 39 2010 W 500 S Salt Lake City UT 84104 EOD 9
41 40 380 W 2880 S Salt Lake City UT 84115 10:30:00 45

View File

@ -0,0 +1,160 @@
# 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')}")