| 
 | 1 | +"""Breath First Search (BFS) can be used when finding the shortest path   | 
 | 2 | +from a given source node to a target node in an unweighted graph.  | 
 | 3 | +"""  | 
 | 4 | +graph = {  | 
 | 5 | +    "A": ["B", "C", "E"],  | 
 | 6 | +    "B": ["A", "D", "E"],  | 
 | 7 | +    "C": ["A", "F", "G"],  | 
 | 8 | +    "D": ["B"],  | 
 | 9 | +    "E": ["A", "B", "D"],  | 
 | 10 | +    "F": ["C"],  | 
 | 11 | +    "G": ["C"],  | 
 | 12 | +}  | 
 | 13 | + | 
 | 14 | +from typing import Dict  | 
 | 15 | + | 
 | 16 | + | 
 | 17 | +class Graph:  | 
 | 18 | +    def __init__(self, graph: Dict[str, str], source_vertex: str) -> None:  | 
 | 19 | +        """Graph is implemented as dictionary of adjancency lists. Also,  | 
 | 20 | +        Source vertex have to be defined upon initialization.  | 
 | 21 | +        """  | 
 | 22 | +        self.graph = graph  | 
 | 23 | +        # mapping node to its parent in resulting breadth first tree  | 
 | 24 | +        self.parent = {}  | 
 | 25 | +        self.source_vertex = source_vertex  | 
 | 26 | + | 
 | 27 | +    def breath_first_search(self) -> None:  | 
 | 28 | +        """This function is a helper for running breath first search on this graph.  | 
 | 29 | +        >>> g = Graph(graph, "G")  | 
 | 30 | +        >>> g.breath_first_search()  | 
 | 31 | +        >>> g.parent  | 
 | 32 | +        {'G': None, 'C': 'G', 'A': 'C', 'F': 'C', 'B': 'A', 'E': 'A', 'D': 'B'}  | 
 | 33 | +        """  | 
 | 34 | +        visited = {self.source_vertex}  | 
 | 35 | +        self.parent[self.source_vertex] = None  | 
 | 36 | +        queue = [self.source_vertex]  # first in first out queue  | 
 | 37 | + | 
 | 38 | +        while queue:  | 
 | 39 | +            vertex = queue.pop(0)  | 
 | 40 | +            for adjancent_vertex in self.graph[vertex]:  | 
 | 41 | +                if adjancent_vertex not in visited:  | 
 | 42 | +                    visited.add(adjancent_vertex)  | 
 | 43 | +                    self.parent[adjancent_vertex] = vertex  | 
 | 44 | +                    queue.append(adjancent_vertex)  | 
 | 45 | + | 
 | 46 | +    def shortest_path(self, target_vertex: str) -> str:  | 
 | 47 | +        """This shortest path function returns a string, describing the result:  | 
 | 48 | +        1.) No path is found. The string is a human readable message to indicate this.  | 
 | 49 | +        2.) The shortest path is found. The string is in the form `v1(->v2->v3->...->vn)`,  | 
 | 50 | +            where v1 is the source vertex and vn is the target vertex, if it exists separately.  | 
 | 51 | +
  | 
 | 52 | +        >>> g = Graph(graph, "G")  | 
 | 53 | +        >>> g.breath_first_search()  | 
 | 54 | +
  | 
 | 55 | +        Case 1 - No path is found.  | 
 | 56 | +        >>> g.shortest_path("Foo")  | 
 | 57 | +        'No path from vertex:G to vertex:Foo'  | 
 | 58 | +
  | 
 | 59 | +        Case 2 - The path is found.  | 
 | 60 | +        >>> g.shortest_path("D")  | 
 | 61 | +        'G->C->A->B->D'  | 
 | 62 | +        >>> g.shortest_path("G")  | 
 | 63 | +        'G'  | 
 | 64 | +        """  | 
 | 65 | +        if target_vertex == self.source_vertex:  | 
 | 66 | +            return f"{self.source_vertex}"  | 
 | 67 | +        elif not self.parent.get(target_vertex):  | 
 | 68 | +            return f"No path from vertex:{self.source_vertex} to vertex:{target_vertex}"  | 
 | 69 | +        else:  | 
 | 70 | +            return self.shortest_path(self.parent[target_vertex]) + f"->{target_vertex}"  | 
 | 71 | + | 
 | 72 | + | 
 | 73 | +if __name__ == "__main__":  | 
 | 74 | +    import doctest  | 
 | 75 | + | 
 | 76 | +    doctest.testmod()  | 
 | 77 | +    g = Graph(graph, "G")  | 
 | 78 | +    g.breath_first_search()  | 
 | 79 | +    print(g.shortest_path("D"))  | 
 | 80 | +    print(g.shortest_path("G"))  | 
 | 81 | +    print(g.shortest_path("Foo"))  | 
0 commit comments