def __init__(self, id: int) -> None: self.id = id self.capacity = 16 self.mph = 18 # Initialize the truck departure time to the start of the delivery day self.departure_time = Clock(8) self.current_time = Clock() self.packages: List[Package] = []
def load_packages(cls) -> Packages: """Loads the package data from a file. Returns ------- HashSet[int, str] The mapping of package identifiers to package objects. Space Complexity --------------- O(n) Time Complexity --------------- O(n) """ data = cls.load_json('data/package_data.json') size = len(data) packages = HashSet(size) for key, value in data.items(): identifier = int(key) (hours, minutes) = map(int, value['deadline'].split(':')) deadline = Clock(hours, minutes) package = Package( identifier, value['address'], value['city'], value['state'], value['zip'], value['kg'], deadline, ) # Delayed packages - will not arrive at depot until 09:05 if package.id in [6, 25, 28, 32]: package.arrival_time = Clock(9, 5) # Incorrect address - will be corrected at 10:20 if package.id == 9: package.street = '410 S State St' package.arrival_time = Clock(10, 20) # Package must be delivered via truck two if package.id in [3, 18, 36, 38]: package.deliverable_by = [2] package.is_priority = True # Package must be delivered with linked packages if package.id in [13, 14, 15, 16, 19, 20]: package.linked = True package.is_priority = True packages.set(identifier, package) return packages
def package_report(self) -> None: """Prints a report of a single package at a specific time. Space Complexity --------------- O(n) Time Complexity --------------- O(n) """ package_id = self.prompter.prompt('package') try: package_id = int(package_id) except: print('\nInvalid package identifier\n') return if package_id not in self.depot.package_table.packages: print('\nInvalid package identifier\n') return time = self.prompter.prompt('time') if match(r'^\d{2}:\d{2}:\d{2}$', time) is None: print('\nInvalid time format\n') return (hours, minutes, seconds) = map(int, time.split(':')) package = self.depot.package_table.get(package_id) print('\nWGUPS Individual Package Report\n') print(f'Package: {package_id}') print(f'Time: {time}') print(package.inline_report(Clock(hours, minutes))) print('\n')
def packages_report(self) -> None: """Prints a report of the status of all packages at a specific time. Time Complexity --------------- O(n) Time Complexity --------------- O(n*log(n)) """ time = self.prompter.prompt('time') if match(r'^\d{2}:\d{2}:\d{2}$', time) is None: print('\nInvalid time format\n') return (hours, minutes, seconds) = map(int, time.split(':')) packages = sorted(self.depot.package_table.all(), key=lambda p: p.id) reports = [ package.delivery_report(Clock(hours, minutes)) for package in packages ] col_width = max(len(item) for report in reports for item in report) + 2 # Padding print('\nWGUPS Comprehensive Package Report\n') print(f'Time: {time}\n') for report in reports: print(''.join(item.ljust(col_width) for item in report)) print('\n')
def deliver_packages(self, distance_table: DistanceTable, return_to_depot: bool) -> None: """Delivers all packages currently loaded on the truck. Parameters ---------- distance_table : DistanceTable A table of addresses and the distances between them. return_to_depot : bool Whether or not the truck should return to the depot after finishing its deliveries. Space Complexity --------------- O(n^2) Time Complexity --------------- O(n^2*log(n)) """ current_location = distance_table.depot_address destinations = self.destinations() total_time = self.departure_time total_distance = 0 while self.packages: destinations = sorted( destinations, key=lambda x: distance_table.distance(current_location, x)) closest = destinations.pop(0) distance = distance_table.distance(current_location, closest) travel_time = self.travel_time(distance) total_time.add_minutes(travel_time) deliveries = [ package for package in self.packages if package.street == closest ] for package in deliveries: self.packages.remove(package) package.deliver(total_time.clone()) current_location = closest total_distance += distance if return_to_depot: distance = distance_table.to_depot(current_location) travel_time = self.travel_time(distance) total_distance += distance total_time.add_minutes(travel_time) self.current_time = Clock( self.current_time.hours + total_time.hours, self.current_time.minutes + total_time.minutes) return total_distance
def __init__(self, id: int, street: str, city: str, state: str, zip_code: str, weight: int, deadline: Clock, arrival_time: Clock = Clock(8)) -> None: self.id = id self.street = street self.city = city self.state = state self.zip_code = zip_code self.weight = weight self.deadline = deadline self.status = PackageStatus.AWAITING_DELIVERY self.linked = False self.deliverable_by = [1, 2] self.is_priority = False self.arrival_time = Clock(8) self.pickup_time = None self.delivery_time = None
def is_high_priority(self) -> bool: """Determines if the package is high priority or not. Returns ------- bool Returns `True` if the package is high priority, otherwise returns `False`. Space Complexity --------------- O(1) Time Complexity --------------- O(1) """ return self.deadline < Clock(17) or self.is_priority
class Truck: """A class which represents a truck delivering packages for the WGUPS. Attributes ---------- id : int The identifier for the truck. capacity : int The total number of possible packages that can be carried by the truck. mph : int The speed of the truck. departure_time : Clock The earliest time that the truck can leave the hub. packages : List[Package] The packages that have been loaded onto the truck. """ id: int capacity: int mph: int departure_time: Clock packages: List[Package] def __init__(self, id: int) -> None: self.id = id self.capacity = 16 self.mph = 18 # Initialize the truck departure time to the start of the delivery day self.departure_time = Clock(8) self.current_time = Clock() self.packages: List[Package] = [] def is_full(self) -> bool: """Determines if the truck is full. Returns ------- bool Returns `True` if the truck is full, otherwise returns `False`. Space Complexity --------------- O(1) Time Complexity --------------- O(1) """ return len(self.packages) >= self.capacity def depart_at(self, time: Clock) -> None: """Sets the departure time of the truck. Parameters ---------- time : Clock The departure time to set. Space Complexity --------------- O(1) Time Complexity --------------- O(1) """ self.departure_time = time self.current_time = self.departure_time if self.departure_time > self.current_time else self.current_time def destinations(self) -> List[str]: """Gets the list of destinations that will be visited by the truck. Returns ------- List[str] The list of destinations that will be visited by the truck. Space Complexity --------------- O(n) Time Complexity --------------- O(n) """ return [package.street for package in self.packages] def can_load(self, n: int) -> bool: """Determines if the truck can accept `n` number of packages without exceeding its capacity. Parameters ---------- n : int The number of packages. Returns ------- bool Returns `True` if the truck can accept all `n` packages, otherwise returns `False`. Space Complexity --------------- O(1) Time Complexity --------------- O(1) """ return len(self.packages) + n <= self.capacity def load_package(self, package: Package) -> None: """Loads a package onto the truck. Space Complexity --------------- O(1) Time Complexity --------------- O(1) """ package.pickup(self.departure_time.clone()) self.packages.append(package) def load_packages(self, packages: List[Package]) -> None: """Loads several packages onto the truck. Space Complexity --------------- O(n) Time Complexity --------------- O(n) """ for package in packages: self.load_package(package) def unload_package(self, package: Package) -> None: """Unloads a package from the truck. Space Complexity --------------- O(1) Time Complexity --------------- O(1) """ if package in self.packages: self.packages.remove(package) def unload_packages(self, packages: List[Package]) -> None: """Unloads several packages from the truck. Space Complexity --------------- O(n) Time Complexity --------------- O(n) """ for package in packages: if package in self.packages: self.packages.remove(package) def has_package(self, package: Package) -> bool: """Determines if the truck contains the specified package. Returns ------- bool True if the truck contains the specified package, otherwise False Space Complexity --------------- O(1) Time Complexity --------------- O(1) """ return package in self.packages def can_deliver(self, package: Package) -> bool: """Determines if the truck can deliver the specified package. Parameters ---------- package : Package The package to check. Returns ------- bool Returns `True` if the truck can deliver the package, otherwise returns `False`. Space Complexity --------------- O(1) Time Complexity --------------- O(1) """ return self.id in package.deliverable_by and self.departure_time >= package.arrival_time def deliver_packages(self, distance_table: DistanceTable, return_to_depot: bool) -> None: """Delivers all packages currently loaded on the truck. Parameters ---------- distance_table : DistanceTable A table of addresses and the distances between them. return_to_depot : bool Whether or not the truck should return to the depot after finishing its deliveries. Space Complexity --------------- O(n^2) Time Complexity --------------- O(n^2*log(n)) """ current_location = distance_table.depot_address destinations = self.destinations() total_time = self.departure_time total_distance = 0 while self.packages: destinations = sorted( destinations, key=lambda x: distance_table.distance(current_location, x)) closest = destinations.pop(0) distance = distance_table.distance(current_location, closest) travel_time = self.travel_time(distance) total_time.add_minutes(travel_time) deliveries = [ package for package in self.packages if package.street == closest ] for package in deliveries: self.packages.remove(package) package.deliver(total_time.clone()) current_location = closest total_distance += distance if return_to_depot: distance = distance_table.to_depot(current_location) travel_time = self.travel_time(distance) total_distance += distance total_time.add_minutes(travel_time) self.current_time = Clock( self.current_time.hours + total_time.hours, self.current_time.minutes + total_time.minutes) return total_distance def travel_time(self, miles: int) -> int: """Returns the time in minutes that it will take the truck to travel the specified number of miles. Parameters ---------- miles : int The number of miles that will be traveled. Returns ------- int The time in minutes that it takes to travel the specified number of miles. Space Complexity --------------- O(1) Time Complexity --------------- O(1) """ return round((miles / self.mph) * 60)
def deliver_packages(self) -> float: """Returns the total distance traveled by trucks during package delivery. Returns ------- float The total distance. Space Complexity --------------- O(n^3) Time Complexity --------------- O(n^3*log(n)) """ packages = self.package_table.all() # Obtain separate lists of the high and low priority packages that must be delivered # and sort them by deadline (if applicable) and their distance to the depot high_priority = sorted( [package for package in packages if package.is_high_priority()], key=lambda x: (x.deadline, self.distance_table.to_depot(x.street))) regular_priority = sorted( [ package for package in packages if not package.is_high_priority() ], key=lambda x: self.distance_table.to_depot(x.street)) # Initialize loop parameters trip = 0 truck_index = 0 delivered = 0 total_distance = 0 departure_times = [Clock(8), Clock(9, 5), Clock(10, 20)] # Continue delivering packages while the number of packages that have been delivered # is less than the total number of packages that need to be delivered. The loop will # iterate three times in total given that our truck capacity is 16 and the trucks are # always loaded to capacity while delivered < len(packages): # Obtain the truck and set its departure time truck: Truck = self.trucks.get(truck_index) truck.depart_at(departure_times[trip].clone()) # Obtain the priority packages that are deliverable by the truck priority_deliveries = [ package for package in high_priority if truck.can_deliver(package) ] # Obtain the regular priority packages that are deliverable by the truck regular_deliveries = [ package for package in regular_priority if truck.can_deliver(package) ] # First, load all priority deliveries that fit on the truck for package in priority_deliveries: if not truck.is_full() and not truck.has_package(package): high_priority.remove(package) truck.load_package(package) delivered += 1 # Then, load all regular priority deliveries that fit on the truck for package in regular_deliveries: if not truck.is_full() and not truck.has_package(package): regular_priority.remove(package) truck.load_package(package) delivered += 1 # Calculate the total distance traveled by the truck in addition to the # distance to return to the depot, if necessary remaining_packages = len(packages) - delivered return_to_depot = remaining_packages > 0 total_distance += truck.deliver_packages(self.distance_table, return_to_depot) # Increment the trip index and the truck index trip += 1 truck_index = trip % len(self.trucks) return total_distance