def min_route_distance(self, node_from, node_to): """ Gets the shortest route between two points, using Dijkstra's algorithm. This version is modified to allow for a route that traverses back to the origin if such a route is possible. Arguments: node_from - The origin point of the connection being queried. Method throws a ValueError if the node does not exist. node_to - The destination of the connection being queried. Method throws a ValueError if the node does not exist. Returns: int/float - The minimum distance to traverse to complete the shortest possible route. If the route is origin to origin and no path can be made that traverses other routes to get back to the origin, 0 is returned. If it is impossible to get from the origin to the destination when the two are different, float("inf") (i.e. infinity) is returned. """ if self.get(node_from) is None or self.get(node_to) is None: raise ValueError("Non-existant origin or destination node was " \ "given") min_distances = {} node_queue = PriorityQueue() # First, initialize min_distances with assumed min_distances for # the time being (i.e. 0 for origin, float("inf") for all possible # destinations for key in self._nodes.keys(): if key == node_from: init_minimum = 0 else: init_minimum = float("inf") min_distances[key] = init_minimum # Items in the priority queue are tuples, with the min_distance # listed first for sorting, then the key of the final point node_queue.add((init_minimum, key)) while not node_queue.is_empty(): minimum, current_node = node_queue.remove() for connected_node, connected_distance in self.get( current_node).items(): new_distance = minimum + connected_distance # If the minimum distance from here plus the distance to the # next node is less than the current minimum distance to that # node from the origin, replace it # OR - if the distance is the origin (i.e. min_distance # from origin to connected_node is zero) - this allows us to # loop back to the origin! if new_distance < min_distances[connected_node] or \ min_distances[connected_node] == 0: min_distances[connected_node] = new_distance # Prevents the origin node from being added back into the # queue, so we don't re-evaluate the whole graph again if not connected_node == node_from: node_queue.add((new_distance, connected_node)) # Now we have a dict of all of the Dijkstra min distances to # those points from the origin -- we want to return the distance # to just one point (the destination) return min_distances[node_to]
def _num_trips(self, node_from, node_to, minimum, maximum, is_distance): """ Determines the number of trips that can be made between two points within a certain number of moves, or less than a certain distance. Arguments: node_from - The origin point of the connection being queried. Method throws a ValueError if the node does not exist. node_to - The destination of the connection being queried. Method throws a ValueError if the node does not exist. minimum - The minimum moves/distance required to be made before trips are counted. Method will throw a ValueError if less than zero. maximum - The maximum moves/distance until the number of trips stop being counted and the method exits. Will throw a ValueError if less than minimum. is_distance - Whether or not we're calculating the number of trips possible based on distance (True) or number of moves (False). Returns: int - The number of possible trips given the restrictions. """ if maximum < minimum: raise ValueError("maximum ({:d}) is less than " \ "minimum ({:d})".format(maximum, minimum)) elif minimum <= 0: raise ValueError("minimum requires a value greater than zero") if self.get(node_from) is None or self.get(node_to) is None: raise ValueError("Non-existant origin or destination node was " \ "given") available_routes = 0 # We start by building a priority queue made of tuples, which contains # origin node and the number of moves or distance needed to get there. # Then, we start populating the queue with the # name of connecting nodes and the number of connections traversed # or distance traversed to get from the origin to that point. trip_queue = PriorityQueue() trip_queue.add((0, node_from)) while not trip_queue.is_empty(): current_node = trip_queue.remove() # leave the loop now if we've reached the maximum. # in the current data set, our maximum move count limit is based # on being less than OR EQUAL TO the maximum, while the distance # limit is based on being LESS THAN to the maximum ONLY. That # conditional is reflected here. if is_distance and current_node[0] >= maximum: break elif not is_distance and current_node[0] > maximum: break # add to the count if we've reached the destination and # we're above our minimum if current_node[1] == node_to and current_node[0] >= minimum: available_routes += 1 # continue adding to the queue - if we're measuring by distance, # add the distance to the next node. Else, just increment by one # and keep going. for connected_node, distance in self.get(current_node[1]).items(): if is_distance: increment = distance else: increment = 1 trip_queue.add((current_node[0] + increment, connected_node)) return available_routes