def load_map(map_filename): """ Parses the map file and constructs a directed graph Parameters: map_filename : name of the map file Assumes: Each entry in the map file consists of the following four positive integers, separated by a blank space: From To TotalDistance DistanceOutdoors e.g. 32 76 54 23 This entry would become an edge from 32 to 76. Returns: a Digraph representing the map """ # TODO print("Loading map from file...") # Format for the graph file: # Starting node, destination node, total weight of edge (distance), total distance spent outside # So steps: # 1. Create graph object # For every line: # 0. Save all numbers in variables (source, destination, total_distance, outdoor_distance) # 1. Create source_node object # 2.Create destination node object # 3. If graph has not(source_node) object: # add_node to graph # else get node from graph and save in src variable # Do the same for destination object # 4. Create weightedEdge object from variables and objects # 5. Add edge to graph # Problem 2c: Testing load_map # Include the lines used to test load_map below, but comment them out g = Digraph() with open(map_filename) as f: for line in f: input = line.split() source = input[0] destination = input[1] total_distance = input[2] outdoor_distance = input[3] src_object = Node(source) dest_object = Node(destination) if not (g.has_node(src_object)): g.add_node(src_object) if not (g.has_node(dest_object)): g.add_node(dest_object) edge_object = WeightedEdge(src_object, dest_object, total_distance, outdoor_distance) if edge_object not in g.get_edges_for_node(src_object): g.add_edge(edge_object) return g
def directed_cyclic_bfs(digraph: Digraph, start: str, end: str, max_total_dist: int, max_dist_outdoors: int) -> Optional[List[str]]: #([path], total_path_distance, total_path_outdoor_distance) initPath: Tuple[List[str], int, int] = ([start], 0, 0 ) #Caution: 0 is a dangerous integer to work with queue: List[Tuple[List[str], int, int]] = [initPath] bestDist: int = 0 bestPath: Optional[List[str]] = None while len(queue) != 0: pathDetails: Tuple[List[str], int, int] = queue.pop(0) currentPath: List[str] totalPathDist: int totalOutdoorDist: int currentPath, totalPathDist, totalOutdoorDist = pathDetails lastNode: Node = Node(currentPath[-1]) ## print("current path:", printPath(currentPath)) #if the length of the currentPath is greater than the best path #it means the search went down one level, so no need to keep searching if bestPath and len(bestPath) < len(currentPath): break if totalPathDist > max_total_dist or totalOutdoorDist > max_dist_outdoors: continue if lastNode.get_name( ) == end: #shortest path not necessarily best path, ie has to be shortest and best dist if not bestDist or totalPathDist < bestDist: #set once or change if found better bestDist = totalPathDist bestPath = currentPath[:] ## print("found") #we continue the search along the same level/generation #because the next path might contain a shorter distance #continue DOWN the search only if we havent found the bestPath if not bestPath: for edge in digraph.get_edges_for_node(lastNode): w_edge = cast(WeightedEdge, edge) childNode: Node = w_edge.get_destination() newPath: List[str] = currentPath + [childNode.get_name()] newPathDist: int = totalPathDist + w_edge.get_total_distance() newOutDist: int = totalOutdoorDist + w_edge.get_outdoor_distance( ) newPathDetails: Tuple[List[str], int, int] = \ (newPath, newPathDist, newOutDist) if childNode not in currentPath: #if kid points back a generation, discard if newPathDist <= max_total_dist and \ newOutDist <= max_dist_outdoors: queue.append(newPathDetails) return bestPath
def load_map(map_filename): """ Parses the map file and constructs a directed graph Parameters: map_filename : name of the map file Assumes: Each entry in the map file consists of the following four positive integers, separated by a blank space: From To TotalDistance DistanceOutdoors e.g. 32 76 54 23 This entry would become an edge from 32 to 76. Returns: a Digraph representing the map """ graph = Digraph() file = open(map_filename, "r") print("Loading map from file...") for line in file: if (line == ""): break data = line.strip().split(' ') src = Node(data[0]) dest = Node(data[1]) edge = WeightedEdge(src, dest, data[2], data[3]) try: graph.add_node(src) except ValueError: # print("Mult childs node "+str(src)) pass try: graph.add_node(dest) except ValueError: pass if (edge not in graph.get_edges_for_node(src)): graph.add_edge(edge) else: print("Dupliate edge: " + str(edge)) file.close() return graph
def load_map(map_filename): """ Parses the map file and constructs a directed graph Parameters: map_filename : name of the map file Assumes: Each entry in the map file consists of the following four positive integers, separated by a blank space: From To TotalDistance DistanceOutdoors e.g. 32 76 54 23 This entry would become an edge from 32 to 76. Returns: a Digraph representing the map """ mitMap = Digraph() mapFileInfo = '' with open(map_filename, 'r') as mapFile: mapFileInfo = mapFile.read() mapFile.closed mapList = str.splitlines(mapFileInfo, False) for m in mapList: mapData = str.split(m, ' ') fromNode, toNode = Node(mapData[0]), Node(mapData[1]) edge = WeightedEdge(fromNode, toNode, mapData[2], mapData[3]) if not mitMap.has_node(fromNode): mitMap.add_node(fromNode) if not mitMap.has_node(toNode): mitMap.add_node(toNode) if edge not in mitMap.get_edges_for_node(fromNode): mitMap.add_edge(edge) print("Loading map from file...") return mitMap
def get_best_path( digraph: Digraph, start: str, end: str, # path: List[List[str], int, int], #this is what was suppposed to be path: List[str], max_dist_outdoors: int, total_dist: int = 0, best_dist: int = 0, best_path: List[str] = []) -> Optional[Tuple[List[str], int]]: """ Finds the shortest path between buildings subject to constraints. Parameters: digraph: Digraph instance The graph on which to carry out the search start: string Building number at which to start end: string Building number at which to end path: list composed of [[list of strings], int, int] Represents the current path of nodes being traversed. Contains a list of node names, total distance traveled, and total distance outdoors. max_dist_outdoors: int Maximum distance spent outdoors on a path total_dist: int Total distance travelled by a single path. If path == best_path then total_dist == best_dist best_dist: int The smallest distance between the original start and end node for the initial problem that you are trying to solve best_path: list of strings The shortest path found so far between the original start and end node. Returns: A tuple with the shortest-path from start to end, represented by a list of building numbers (in strings), [n_1, n_2, ..., n_k], where there exists an edge from n_i to n_(i+1) in digraph, for all 1 <= i < k and the distance of that path. If there exists no path that satisfies max_total_dist and max_dist_outdoors constraints, then return None. """ startNode: Node = Node(start) endNode: Node = Node(end) if not (digraph.has_node(startNode) and digraph.has_node(endNode)): raise ValueError("Invalid start and/or end nodes") path = path + [start] if start == end: return path, total_dist #To be considered ## if max_dist_outdoors <= 0: ## return None if max_dist_outdoors >= 0: for edge in digraph.get_edges_for_node(startNode): w_edge = cast(WeightedEdge, edge) child_node: Node = w_edge.get_destination() child = child_node.get_name() outdoor_dist: int = w_edge.get_outdoor_distance() dist_travelled: int = w_edge.get_total_distance() if (child not in path and outdoor_dist <= max_dist_outdoors): if best_dist == 0 or (total_dist < best_dist and len(path) <= len(best_path)): #This evaluates to the best path. Only enter here for the best #path newPathDist = get_best_path( digraph, child, end, path, max_dist_outdoors - outdoor_dist, total_dist + dist_travelled, best_dist, best_path) if newPathDist != None: newPathDist = cast(Tuple[List[str], int], newPathDist) if (best_dist and newPathDist[1] < best_dist) or not best_dist: best_path, best_dist = newPathDist return None if not best_path else (best_path, best_dist)