From c65f91db51c73e46082514e6ed9b01c6b9419139 Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Thu, 16 Apr 2020 14:50:44 +0800 Subject: [PATCH 01/15] Create breadth_first_search_shortest_path.py --- graphs/breadth_first_search_shortest_path.py | 50 ++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 graphs/breadth_first_search_shortest_path.py diff --git a/graphs/breadth_first_search_shortest_path.py b/graphs/breadth_first_search_shortest_path.py new file mode 100644 index 000000000000..2c3a8369020d --- /dev/null +++ b/graphs/breadth_first_search_shortest_path.py @@ -0,0 +1,50 @@ +class Graph: + def __init__(self, graph, source_vertex): + """graph is implemented as dictionary of adjancency lists""" + self.graph = graph + # mapping node to its parent in resulting breadth first tree + self.parent = {} + self.source_vertex = source_vertex + + def breath_first_search(self): + """ + """ + visited = {self.source_vertex} + self.parent[self.source_vertex] = None + queue = [self.source_vertex] # first in first out queue + + while queue: + vertex = queue.pop(0) + for adjancent_vertex in self.graph[vertex]: + if adjancent_vertex not in visited: + visited.add(adjancent_vertex) + self.parent[adjancent_vertex] = vertex + queue.append(adjancent_vertex) + + def print_shortest_path(self, target_vertex): + if target_vertex == self.source_vertex: + print(self.source_vertex, end="") + elif target_vertex not in self.parent or not self.parent[target_vertex]: + print(f"No path from vertex:{self.source_vertex} to vertex:{target_vertex}") + else: + self.print_shortest_path(self.parent[target_vertex]) + print(f"->{target_vertex}", end="") + + +if __name__ == "__main__": + graph = { + "A": ["B", "C", "E"], + "B": ["A", "D", "E"], + "C": ["A", "F", "G"], + "D": ["B"], + "E": ["A", "B", "D"], + "F": ["C"], + "G": ["C"], + } + g = Graph(graph, "G") + g.breath_first_search() + g.print_shortest_path("D") + print() + g.print_shortest_path("G") + print() + g.print_shortest_path("Foo") From 9f528c91d856a8c23308ca04f5d0e081652dbab4 Mon Sep 17 00:00:00 2001 From: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Date: Thu, 16 Apr 2020 06:51:00 +0000 Subject: [PATCH 02/15] updating DIRECTORY.md --- DIRECTORY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/DIRECTORY.md b/DIRECTORY.md index 0f7363d7e9ab..3d5e71a0516f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -209,6 +209,7 @@ * [Bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs.py) * [Bfs Shortest Path](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs_shortest_path.py) * [Breadth First Search](https://github.com/TheAlgorithms/Python/blob/master/graphs/breadth_first_search.py) + * [Breadth First Search Shortest Path](https://github.com/TheAlgorithms/Python/blob/master/graphs/breadth_first_search_shortest_path.py) * [Check Bipartite Graph Bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_bfs.py) * [Check Bipartite Graph Dfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_dfs.py) * [Depth First Search](https://github.com/TheAlgorithms/Python/blob/master/graphs/depth_first_search.py) From b64027bcd6317ee7647c9951f14e5e2c3d0e011d Mon Sep 17 00:00:00 2001 From: John Law Date: Mon, 27 Apr 2020 18:05:25 +0200 Subject: [PATCH 03/15] Reduce side effect of `shortest_path` For the sake of future testing and documentation - --- graphs/breadth_first_search_shortest_path.py | 31 ++++++++++++-------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/graphs/breadth_first_search_shortest_path.py b/graphs/breadth_first_search_shortest_path.py index 2c3a8369020d..2e049dd26485 100644 --- a/graphs/breadth_first_search_shortest_path.py +++ b/graphs/breadth_first_search_shortest_path.py @@ -1,13 +1,18 @@ +"""Breath First Search (BFS) can be used when finding the shortest path +from a given source node to a target node in an unweighted graph. +""" class Graph: def __init__(self, graph, source_vertex): - """graph is implemented as dictionary of adjancency lists""" + """Graph is implemented as dictionary of adjancency lists. Also, + Source vertex have to be defined upon initialization. + """ self.graph = graph # mapping node to its parent in resulting breadth first tree self.parent = {} self.source_vertex = source_vertex def breath_first_search(self): - """ + """This function is a helper for running breath first search on this graph. """ visited = {self.source_vertex} self.parent[self.source_vertex] = None @@ -21,14 +26,18 @@ def breath_first_search(self): self.parent[adjancent_vertex] = vertex queue.append(adjancent_vertex) - def print_shortest_path(self, target_vertex): + def shortest_path(self, target_vertex): + """This shortest path function returns a string, describing the result: + 1.) No path is found. The string is a human readable message to indicate this. + 2.) The shortest path is found. The string is in the form `v1(->v2->v3->...->vn)`, + where v1 is the source vertex and vn is the target vertex, if it exists seperately. + """ if target_vertex == self.source_vertex: - print(self.source_vertex, end="") + return f"{self.source_vertex}" elif target_vertex not in self.parent or not self.parent[target_vertex]: - print(f"No path from vertex:{self.source_vertex} to vertex:{target_vertex}") + return f"No path from vertex:{self.source_vertex} to vertex:{target_vertex}" else: - self.print_shortest_path(self.parent[target_vertex]) - print(f"->{target_vertex}", end="") + return self.shortest_path(self.parent[target_vertex]) + f"->{target_vertex}" if __name__ == "__main__": @@ -43,8 +52,6 @@ def print_shortest_path(self, target_vertex): } g = Graph(graph, "G") g.breath_first_search() - g.print_shortest_path("D") - print() - g.print_shortest_path("G") - print() - g.print_shortest_path("Foo") + print(g.shortest_path("D")) + print(g.shortest_path("G")) + print(g.shortest_path("Foo")) From fb37f107415ef71439b40ea67ee3eb5ae9717b0c Mon Sep 17 00:00:00 2001 From: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Date: Mon, 27 Apr 2020 16:06:23 +0000 Subject: [PATCH 04/15] fixup! Format Python code with psf/black push --- graphs/breadth_first_search_shortest_path.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/graphs/breadth_first_search_shortest_path.py b/graphs/breadth_first_search_shortest_path.py index 2e049dd26485..f75286645663 100644 --- a/graphs/breadth_first_search_shortest_path.py +++ b/graphs/breadth_first_search_shortest_path.py @@ -1,6 +1,8 @@ """Breath First Search (BFS) can be used when finding the shortest path from a given source node to a target node in an unweighted graph. """ + + class Graph: def __init__(self, graph, source_vertex): """Graph is implemented as dictionary of adjancency lists. Also, From 53a49d70028b828088c6ba5c7b970284f43a79b8 Mon Sep 17 00:00:00 2001 From: John Law Date: Mon, 27 Apr 2020 18:08:57 +0200 Subject: [PATCH 05/15] Fix typo `separately` --- graphs/breadth_first_search_shortest_path.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphs/breadth_first_search_shortest_path.py b/graphs/breadth_first_search_shortest_path.py index f75286645663..913486530233 100644 --- a/graphs/breadth_first_search_shortest_path.py +++ b/graphs/breadth_first_search_shortest_path.py @@ -32,7 +32,7 @@ def shortest_path(self, target_vertex): """This shortest path function returns a string, describing the result: 1.) No path is found. The string is a human readable message to indicate this. 2.) The shortest path is found. The string is in the form `v1(->v2->v3->...->vn)`, - where v1 is the source vertex and vn is the target vertex, if it exists seperately. + where v1 is the source vertex and vn is the target vertex, if it exists separately. """ if target_vertex == self.source_vertex: return f"{self.source_vertex}" From c9ae6f12781315b9cee8840628b5180051b2f10f Mon Sep 17 00:00:00 2001 From: John Law Date: Mon, 27 Apr 2020 18:39:11 +0200 Subject: [PATCH 06/15] Change to get() from dictionary Co-Authored-By: Christian Clauss --- graphs/breadth_first_search_shortest_path.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphs/breadth_first_search_shortest_path.py b/graphs/breadth_first_search_shortest_path.py index 913486530233..6e174169ba3f 100644 --- a/graphs/breadth_first_search_shortest_path.py +++ b/graphs/breadth_first_search_shortest_path.py @@ -36,7 +36,7 @@ def shortest_path(self, target_vertex): """ if target_vertex == self.source_vertex: return f"{self.source_vertex}" - elif target_vertex not in self.parent or not self.parent[target_vertex]: + elif not self.parent.get(target_vertex): return f"No path from vertex:{self.source_vertex} to vertex:{target_vertex}" else: return self.shortest_path(self.parent[target_vertex]) + f"->{target_vertex}" From 0f648a481bce033ff7ae2d6334ee479f64cf9d91 Mon Sep 17 00:00:00 2001 From: John Law Date: Fri, 1 May 2020 01:14:57 +0200 Subject: [PATCH 07/15] Move graph to the top --- graphs/breadth_first_search_shortest_path.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/graphs/breadth_first_search_shortest_path.py b/graphs/breadth_first_search_shortest_path.py index 6e174169ba3f..28468cabbeae 100644 --- a/graphs/breadth_first_search_shortest_path.py +++ b/graphs/breadth_first_search_shortest_path.py @@ -1,7 +1,15 @@ """Breath First Search (BFS) can be used when finding the shortest path from a given source node to a target node in an unweighted graph. """ - +graph = { + "A": ["B", "C", "E"], + "B": ["A", "D", "E"], + "C": ["A", "F", "G"], + "D": ["B"], + "E": ["A", "B", "D"], + "F": ["C"], + "G": ["C"], +} class Graph: def __init__(self, graph, source_vertex): @@ -43,15 +51,6 @@ def shortest_path(self, target_vertex): if __name__ == "__main__": - graph = { - "A": ["B", "C", "E"], - "B": ["A", "D", "E"], - "C": ["A", "F", "G"], - "D": ["B"], - "E": ["A", "B", "D"], - "F": ["C"], - "G": ["C"], - } g = Graph(graph, "G") g.breath_first_search() print(g.shortest_path("D")) From b7039ea2004e859ef2d5cff8d2e2369dcd4f0896 Mon Sep 17 00:00:00 2001 From: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Date: Thu, 30 Apr 2020 23:15:37 +0000 Subject: [PATCH 08/15] fixup! Format Python code with psf/black push --- graphs/breadth_first_search_shortest_path.py | 1 + 1 file changed, 1 insertion(+) diff --git a/graphs/breadth_first_search_shortest_path.py b/graphs/breadth_first_search_shortest_path.py index 28468cabbeae..a68ea362d5d2 100644 --- a/graphs/breadth_first_search_shortest_path.py +++ b/graphs/breadth_first_search_shortest_path.py @@ -11,6 +11,7 @@ "G": ["C"], } + class Graph: def __init__(self, graph, source_vertex): """Graph is implemented as dictionary of adjancency lists. Also, From 264cdfa54a8244f27704c7d5214f8c9f065ca6a6 Mon Sep 17 00:00:00 2001 From: John Law Date: Fri, 1 May 2020 01:17:12 +0200 Subject: [PATCH 09/15] Add doctest for shortest path --- graphs/breadth_first_search_shortest_path.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/graphs/breadth_first_search_shortest_path.py b/graphs/breadth_first_search_shortest_path.py index a68ea362d5d2..663a5aaf9ec3 100644 --- a/graphs/breadth_first_search_shortest_path.py +++ b/graphs/breadth_first_search_shortest_path.py @@ -42,6 +42,16 @@ def shortest_path(self, target_vertex): 1.) No path is found. The string is a human readable message to indicate this. 2.) The shortest path is found. The string is in the form `v1(->v2->v3->...->vn)`, where v1 is the source vertex and vn is the target vertex, if it exists separately. + + >>> g = Graph(graph, "G") + >>> g.breath_first_search() + + Case 1 - No path is found. + >>> assert(g.shortest_path("Foo") == "No path from vertex:G to vertex:Foo") + + Case 2 - The path is found. + >>> assert(g.shortest_path("D") == "G->C->A->B->D") + >>> assert(g.shortest_path("G") == "G") """ if target_vertex == self.source_vertex: return f"{self.source_vertex}" @@ -52,6 +62,8 @@ def shortest_path(self, target_vertex): if __name__ == "__main__": + import doctest + doctest.testmod() g = Graph(graph, "G") g.breath_first_search() print(g.shortest_path("D")) From 4b5e249be055cbb81f6d2cddb5f9fb3148736bbe Mon Sep 17 00:00:00 2001 From: John Law Date: Fri, 1 May 2020 01:17:38 +0200 Subject: [PATCH 10/15] Add doctest for BFS --- graphs/breadth_first_search_shortest_path.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/graphs/breadth_first_search_shortest_path.py b/graphs/breadth_first_search_shortest_path.py index 663a5aaf9ec3..90532d249dfa 100644 --- a/graphs/breadth_first_search_shortest_path.py +++ b/graphs/breadth_first_search_shortest_path.py @@ -24,6 +24,10 @@ def __init__(self, graph, source_vertex): def breath_first_search(self): """This function is a helper for running breath first search on this graph. + >>> g = Graph(graph, "G") + >>> g.breath_first_search() + >>> g.parent + {'G': None, 'C': 'G', 'A': 'C', 'F': 'C', 'B': 'A', 'E': 'A', 'D': 'B'} """ visited = {self.source_vertex} self.parent[self.source_vertex] = None From b9e8404cd623c1589404cbd9224c108f87e88f03 Mon Sep 17 00:00:00 2001 From: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Date: Thu, 30 Apr 2020 23:18:06 +0000 Subject: [PATCH 11/15] fixup! Format Python code with psf/black push --- graphs/breadth_first_search_shortest_path.py | 1 + 1 file changed, 1 insertion(+) diff --git a/graphs/breadth_first_search_shortest_path.py b/graphs/breadth_first_search_shortest_path.py index 90532d249dfa..fdeab5f52020 100644 --- a/graphs/breadth_first_search_shortest_path.py +++ b/graphs/breadth_first_search_shortest_path.py @@ -67,6 +67,7 @@ def shortest_path(self, target_vertex): if __name__ == "__main__": import doctest + doctest.testmod() g = Graph(graph, "G") g.breath_first_search() From e2aad876d3520c562aade13ce3d9951b620632d7 Mon Sep 17 00:00:00 2001 From: John Law Date: Fri, 1 May 2020 01:20:51 +0200 Subject: [PATCH 12/15] Add typings for breadth_first_search_shortest_path --- graphs/breadth_first_search_shortest_path.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/graphs/breadth_first_search_shortest_path.py b/graphs/breadth_first_search_shortest_path.py index fdeab5f52020..2c01c6a81742 100644 --- a/graphs/breadth_first_search_shortest_path.py +++ b/graphs/breadth_first_search_shortest_path.py @@ -11,9 +11,10 @@ "G": ["C"], } +from typing import Dict class Graph: - def __init__(self, graph, source_vertex): + def __init__(self, graph: Dict[str,str], source_vertex: str) -> None: """Graph is implemented as dictionary of adjancency lists. Also, Source vertex have to be defined upon initialization. """ @@ -22,7 +23,7 @@ def __init__(self, graph, source_vertex): self.parent = {} self.source_vertex = source_vertex - def breath_first_search(self): + def breath_first_search(self) -> None: """This function is a helper for running breath first search on this graph. >>> g = Graph(graph, "G") >>> g.breath_first_search() @@ -41,7 +42,7 @@ def breath_first_search(self): self.parent[adjancent_vertex] = vertex queue.append(adjancent_vertex) - def shortest_path(self, target_vertex): + def shortest_path(self, target_vertex: str) -> str: """This shortest path function returns a string, describing the result: 1.) No path is found. The string is a human readable message to indicate this. 2.) The shortest path is found. The string is in the form `v1(->v2->v3->...->vn)`, @@ -67,7 +68,6 @@ def shortest_path(self, target_vertex): if __name__ == "__main__": import doctest - doctest.testmod() g = Graph(graph, "G") g.breath_first_search() From 255832dd20c2c8846a429a10e5d1a4c5742502d2 Mon Sep 17 00:00:00 2001 From: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Date: Thu, 30 Apr 2020 23:21:23 +0000 Subject: [PATCH 13/15] fixup! Format Python code with psf/black push --- graphs/breadth_first_search_shortest_path.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/graphs/breadth_first_search_shortest_path.py b/graphs/breadth_first_search_shortest_path.py index 2c01c6a81742..dde59bef00b7 100644 --- a/graphs/breadth_first_search_shortest_path.py +++ b/graphs/breadth_first_search_shortest_path.py @@ -13,8 +13,9 @@ from typing import Dict + class Graph: - def __init__(self, graph: Dict[str,str], source_vertex: str) -> None: + def __init__(self, graph: Dict[str, str], source_vertex: str) -> None: """Graph is implemented as dictionary of adjancency lists. Also, Source vertex have to be defined upon initialization. """ @@ -68,6 +69,7 @@ def shortest_path(self, target_vertex: str) -> str: if __name__ == "__main__": import doctest + doctest.testmod() g = Graph(graph, "G") g.breath_first_search() From 70f510557d24c72d5aa6e967b4a87662cf7352b3 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 1 May 2020 07:11:24 +0200 Subject: [PATCH 14/15] Remove assert from doctests --- graphs/breadth_first_search_shortest_path.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/graphs/breadth_first_search_shortest_path.py b/graphs/breadth_first_search_shortest_path.py index dde59bef00b7..7d343fb724a0 100644 --- a/graphs/breadth_first_search_shortest_path.py +++ b/graphs/breadth_first_search_shortest_path.py @@ -53,11 +53,13 @@ def shortest_path(self, target_vertex: str) -> str: >>> g.breath_first_search() Case 1 - No path is found. - >>> assert(g.shortest_path("Foo") == "No path from vertex:G to vertex:Foo") - + >>> g.shortest_path("Foo") + 'No path from vertex:G to vertex:Foo' Case 2 - The path is found. - >>> assert(g.shortest_path("D") == "G->C->A->B->D") - >>> assert(g.shortest_path("G") == "G") + >>> g.shortest_path("D") + 'G->C->A->B->D' + >>> g.shortest_path("G") + 'G' """ if target_vertex == self.source_vertex: return f"{self.source_vertex}" From 0e4af153ac564cb7c0d552f4ce2abd6ec9460cea Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 1 May 2020 07:18:24 +0200 Subject: [PATCH 15/15] Add blank line to doctest --- graphs/breadth_first_search_shortest_path.py | 1 + 1 file changed, 1 insertion(+) diff --git a/graphs/breadth_first_search_shortest_path.py b/graphs/breadth_first_search_shortest_path.py index 7d343fb724a0..514aed6d7211 100644 --- a/graphs/breadth_first_search_shortest_path.py +++ b/graphs/breadth_first_search_shortest_path.py @@ -55,6 +55,7 @@ def shortest_path(self, target_vertex: str) -> str: Case 1 - No path is found. >>> g.shortest_path("Foo") 'No path from vertex:G to vertex:Foo' + Case 2 - The path is found. >>> g.shortest_path("D") 'G->C->A->B->D'