diff --git a/graphs/bfs_shortest_path.py b/graphs/bfs_shortest_path.py index ec82c1399..c3664796e 100644 --- a/graphs/bfs_shortest_path.py +++ b/graphs/bfs_shortest_path.py @@ -1,3 +1,11 @@ +"""Breadth-first search shortest path implementations. + + doctest: + python -m doctest -v bfs_shortest_path.py + + Manual test: + python bfs_shortest_path.py +""" graph = { "A": ["B", "C", "E"], "B": ["A", "D", "E"], @@ -9,7 +17,22 @@ graph = { } -def bfs_shortest_path(graph, start, goal): +def bfs_shortest_path(graph: dict, start, goal) -> str: + """Find shortest path between `start` and `goal` nodes. + + Args: + graph (dict): node/list of neighboring nodes key/value pairs. + start: start node. + goal: target node. + + Returns: + Shortest path between `start` and `goal` nodes as a string of nodes. + 'Not found' string if no path found. + + Example: + >>> bfs_shortest_path(graph, "G", "D") + ['G', 'C', 'A', 'B', 'D'] + """ # keep track of explored nodes explored = [] # keep track of all the paths to be checked @@ -44,4 +67,48 @@ def bfs_shortest_path(graph, start, goal): return "So sorry, but a connecting path doesn't exist :(" -bfs_shortest_path(graph, "G", "D") # returns ['G', 'C', 'A', 'B', 'D'] +def bfs_shortest_path_distance(graph: dict, start, target) -> int: + """Find shortest path distance between `start` and `target` nodes. + + Args: + graph: node/list of neighboring nodes key/value pairs. + start: node to start search from. + target: node to search for. + + Returns: + Number of edges in shortest path between `start` and `target` nodes. + -1 if no path exists. + + Example: + >>> bfs_shortest_path_distance(graph, "G", "D") + 4 + >>> bfs_shortest_path_distance(graph, "A", "A") + 0 + >>> bfs_shortest_path_distance(graph, "A", "H") + -1 + """ + if not graph or start not in graph or target not in graph: + return -1 + if start == target: + return 0 + queue = [start] + visited = [start] + # Keep tab on distances from `start` node. + dist = {start: 0, target: -1} + while queue: + node = queue.pop(0) + if node == target: + dist[target] = ( + dist[node] if dist[target] == -1 else min(dist[target], dist[node]) + ) + for adjacent in graph[node]: + if adjacent not in visited: + visited.append(adjacent) + queue.append(adjacent) + dist[adjacent] = dist[node] + 1 + return dist[target] + + +if __name__ == "__main__": + print(bfs_shortest_path(graph, "G", "D")) # returns ['G', 'C', 'A', 'B', 'D'] + print(bfs_shortest_path_distance(graph, "G", "D")) # returns 4