From c5519cd7a2abd7e1aa0721402e6694eb84513789 Mon Sep 17 00:00:00 2001 From: Blas Date: Mon, 9 Jun 2025 15:41:11 -0400 Subject: [PATCH 1/8] Add doctest coverage and type hints to Graph.add_pair --- .../directed_and_undirected_weighted_graph.py | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/graphs/directed_and_undirected_weighted_graph.py b/graphs/directed_and_undirected_weighted_graph.py index 8ca645fdace8..0ae6979a6cda 100644 --- a/graphs/directed_and_undirected_weighted_graph.py +++ b/graphs/directed_and_undirected_weighted_graph.py @@ -268,7 +268,34 @@ def __init__(self): # adding vertices and edges # adding the weight is optional # handles repetition - def add_pair(self, u, v, w=1): + def add_pair(self, u, v, w=1) -> None: + """ + Adds an edge between u and v with an optional weight w to an undirected graph + + >>> g = Graph() + >>> g.add_pair(1,2) + >>> g.graph[1] + [[1, 2]] + >>> g.graph[2] + [[1, 1]] + >>> g.add_pair(1,2) # testing for duplicates + >>> g.graph[1] + [[1, 2]] + >>> g.add_pair(2,1) # reverse order, should not add a duplicate + >>> g.graph[2] + [[1, 1]] + >>> g.add_pair(1,3,5) + >>> g.graph[1] + [[1, 2], [5, 3]] + >>> g.graph[3] + [[5, 1]] + >>> g.add_pair(4,4) # test for self loop + >>> g.graph[4] + [[1, 4]] + >>> g.add_pair(1,2,3) # previously added nodes, different weight + >>> g.graph[1] + [[1, 2], [5, 3], [3, 2]] + """ # check if the u exists if self.graph.get(u): # if there already is a edge From 5835a46c716760e77397ab7e0085abb870e61093 Mon Sep 17 00:00:00 2001 From: Blas Date: Mon, 9 Jun 2025 15:56:25 -0400 Subject: [PATCH 2/8] Add doctest coverage to Graph.remove_pair --- .../directed_and_undirected_weighted_graph.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/graphs/directed_and_undirected_weighted_graph.py b/graphs/directed_and_undirected_weighted_graph.py index 0ae6979a6cda..5f6acf1bce34 100644 --- a/graphs/directed_and_undirected_weighted_graph.py +++ b/graphs/directed_and_undirected_weighted_graph.py @@ -315,6 +315,35 @@ def add_pair(self, u, v, w=1) -> None: # handles if the input does not exist def remove_pair(self, u, v): + """ + Removes the edge between u and v in an unidirected graph, if it exists + + >>> g = Graph() + >>> g.add_pair(1,2) + >>> g.add_pair(1,3,5) + >>> g.graph[1] + [[1, 2], [5, 3]] + >>> g.remove_pair(1, 2) + >>> g.graph[1] + [[5, 3]] + >>> g.graph[2] + [] + >>> g.remove_pair(1,4) # node 4 does not exist + >>> g.remove_pair(10, 11) # neither exists + >>> g.add_pair(5,5) + >>> g.graph[5] + [[1, 5]] + >>> g.remove_pair(5,5) + >>> g.graph[5] + [] + >>> g.add_pair(6,7,2) + >>> g.add_pair(6,7,3) + >>> g.graph[6] + [[2, 7], [3, 7]] + >>> g.remove_pair(6,7) + >>> g.graph[6] + [[3, 7]] + """ if self.graph.get(u): for _ in self.graph[u]: if _[1] == v: From a5a1f385703f7c1b61460a8d2a177c2e9a71d847 Mon Sep 17 00:00:00 2001 From: Blas Date: Mon, 9 Jun 2025 15:57:02 -0400 Subject: [PATCH 3/8] Add type hint to Graph.remove_pair --- graphs/directed_and_undirected_weighted_graph.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphs/directed_and_undirected_weighted_graph.py b/graphs/directed_and_undirected_weighted_graph.py index 5f6acf1bce34..279de97edfc8 100644 --- a/graphs/directed_and_undirected_weighted_graph.py +++ b/graphs/directed_and_undirected_weighted_graph.py @@ -314,10 +314,10 @@ def add_pair(self, u, v, w=1) -> None: self.graph[v] = [[w, u]] # handles if the input does not exist - def remove_pair(self, u, v): + def remove_pair(self, u, v) -> None: """ Removes the edge between u and v in an unidirected graph, if it exists - + >>> g = Graph() >>> g.add_pair(1,2) >>> g.add_pair(1,3,5) From ebb8472bd0e8a771d147e7538d3f7c4f95d5b3f3 Mon Sep 17 00:00:00 2001 From: Blas Date: Mon, 9 Jun 2025 16:07:35 -0400 Subject: [PATCH 4/8] Add doctests and type hints to Graph.dfs --- .../directed_and_undirected_weighted_graph.py | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/graphs/directed_and_undirected_weighted_graph.py b/graphs/directed_and_undirected_weighted_graph.py index 279de97edfc8..015364b7612a 100644 --- a/graphs/directed_and_undirected_weighted_graph.py +++ b/graphs/directed_and_undirected_weighted_graph.py @@ -355,7 +355,32 @@ def remove_pair(self, u, v) -> None: self.graph[v].remove(_) # if no destination is meant the default value is -1 - def dfs(self, s=-2, d=-1): + def dfs(self, s=-2, d=-1) -> None: + """ + Performs a depth-first search starting from node s. + If destination d is given, stops when d is found + + >>> g = Graph() + >>> g.add_pair(1,2) + >>> g.add_pair(2,3) + >>> g.dfs(1) + [1, 2, 3] + >>> g.dfs(1,3) + [1, 2, 3] + >>> g.dfs(1,4) # 4 not in graph + [1, 2, 3] + >>> g.dfs(1,1) # start equals dest + [] + >>> g2 = Graph() + >>> g2.add_pair(10,20) + >>> g2.add_pair(20,30) + >>> g2.dfs() # default start + [10, 20, 30] + >>> g2.add_pair(30,40) + >>> g2.add_pair(40, 50) + >>> g2.dfs(d=40) # checking if destination works properly + [10, 20, 30, 40] + """ if s == d: return [] stack = [] From a056d2e75b856047d035c9a63bbfae8dd2b0ff00 Mon Sep 17 00:00:00 2001 From: Blas Date: Mon, 9 Jun 2025 16:21:17 -0400 Subject: [PATCH 5/8] Add doctests and type hints to Graph.bfs --- .../directed_and_undirected_weighted_graph.py | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/graphs/directed_and_undirected_weighted_graph.py b/graphs/directed_and_undirected_weighted_graph.py index 015364b7612a..45cbc8b3804a 100644 --- a/graphs/directed_and_undirected_weighted_graph.py +++ b/graphs/directed_and_undirected_weighted_graph.py @@ -430,7 +430,32 @@ def fill_graph_randomly(self, c=-1): if n != i: self.add_pair(i, n, 1) - def bfs(self, s=-2): + def bfs(self, s=-2) -> list[int]: + """ + Performs breadth-first search starting from node s. + If s is not given, starts from the first node in the graph + + >>> g = Graph() + >>> g.add_pair(1,2) + >>> g.add_pair(1,3) + >>> g.add_pair(2,4) + >>> g.add_pair(3,5) + >>> g.bfs(1) + [1, 2, 3, 4, 5] + >>> g.bfs(2) + [2, 1, 4, 3, 5] + >>> g.bfs(4) # leaf node test + [4, 2, 1, 3, 5] + >>> g.bfs(10) # nonexistent node + Traceback (most recent call last): + ... + KeyError: 10 + >>> g2 = Graph() + >>> g2.add_pair(10,20) + >>> g2.add_pair(20,30) + >>> g2.bfs() + [10, 20, 30] + """ d = deque() visited = [] if s == -2: From 29be7b6f6541099c1e5f2835fb867bf0a850e3f4 Mon Sep 17 00:00:00 2001 From: Blas Date: Mon, 9 Jun 2025 16:43:05 -0400 Subject: [PATCH 6/8] Add doctest and type hints to Graph.has_cycle; note cycle detection limitations for disconnected graphs --- .../directed_and_undirected_weighted_graph.py | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/graphs/directed_and_undirected_weighted_graph.py b/graphs/directed_and_undirected_weighted_graph.py index 45cbc8b3804a..ef224318b40c 100644 --- a/graphs/directed_and_undirected_weighted_graph.py +++ b/graphs/directed_and_undirected_weighted_graph.py @@ -435,6 +435,9 @@ def bfs(self, s=-2) -> list[int]: Performs breadth-first search starting from node s. If s is not given, starts from the first node in the graph + Returns: + list of nodes found after performing breadth-first search + >>> g = Graph() >>> g.add_pair(1,2) >>> g.add_pair(1,3) @@ -527,7 +530,42 @@ def cycle_nodes(self): if len(stack) == 0: return list(anticipating_nodes) - def has_cycle(self): + def has_cycle(self) -> bool: + """ + Detects whether the undirected graph contains a cycle. + + Note: + - This function assumes the graph is connected and only traverses from the first node found in the graph. + - It does not detect cycles that exist in disconnected components. + - It also does not detect self-loops (e.g., an edge from a node to itself like 1-1). + + Returns: + bool: True if a cycle is detected in the connected component starting from the first node; False otherwise. + + >>> g = Graph() + >>> g.add_pair(1, 2) + >>> g.add_pair(2, 3) + >>> g.has_cycle() + False + >>> g2 = Graph() + >>> g2.add_pair(1, 2) + >>> g2.add_pair(2, 3) + >>> g2.add_pair(3, 1) # creates a cycle + >>> g2.has_cycle() + True + >>> g3 = Graph() + >>> g3.add_pair(1, 1) # self-loop + >>> g3.has_cycle() # Self-loops are not detected by this method + False + >>> g4 = Graph() + >>> g4.add_pair(1, 2) + >>> g4.add_pair(3, 4) + >>> g4.add_pair(4, 5) + >>> g4.add_pair(5, 3) # cycle in disconnected component + >>> g4.has_cycle() # Only checks the component reachable from the first node (1) + False + """ + stack = [] visited = [] s = next(iter(self.graph)) From 0daf7356649e6918f7b9e7d41ee7a428c0b77021 Mon Sep 17 00:00:00 2001 From: Blas Date: Mon, 9 Jun 2025 18:14:32 -0400 Subject: [PATCH 7/8] Fix code style issues flagged by CI --- .../directed_and_undirected_weighted_graph.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/graphs/directed_and_undirected_weighted_graph.py b/graphs/directed_and_undirected_weighted_graph.py index ef224318b40c..8ca6bbbdf236 100644 --- a/graphs/directed_and_undirected_weighted_graph.py +++ b/graphs/directed_and_undirected_weighted_graph.py @@ -355,7 +355,7 @@ def remove_pair(self, u, v) -> None: self.graph[v].remove(_) # if no destination is meant the default value is -1 - def dfs(self, s=-2, d=-1) -> None: + def dfs(self, s=-2, d=-1) -> list[int]: """ Performs a depth-first search starting from node s. If destination d is given, stops when d is found @@ -459,7 +459,7 @@ def bfs(self, s=-2) -> list[int]: >>> g2.bfs() [10, 20, 30] """ - d = deque() + d: deque = deque() visited = [] if s == -2: s = next(iter(self.graph)) @@ -535,12 +535,15 @@ def has_cycle(self) -> bool: Detects whether the undirected graph contains a cycle. Note: - - This function assumes the graph is connected and only traverses from the first node found in the graph. + - This function assumes the graph is connected and only traverses from the + first node found in the graph. - It does not detect cycles that exist in disconnected components. - - It also does not detect self-loops (e.g., an edge from a node to itself like 1-1). + - It also does not detect self-loops + (e.g., an edge from a node to itself like 1-1). Returns: - bool: True if a cycle is detected in the connected component starting from the first node; False otherwise. + bool: True if a cycle is detected in the connected component starting + from the first node; False otherwise. >>> g = Graph() >>> g.add_pair(1, 2) @@ -562,7 +565,7 @@ def has_cycle(self) -> bool: >>> g4.add_pair(3, 4) >>> g4.add_pair(4, 5) >>> g4.add_pair(5, 3) # cycle in disconnected component - >>> g4.has_cycle() # Only checks the component reachable from the first node (1) + >>> g4.has_cycle() # Only checks the component reachable from the first node 1 False """ @@ -572,7 +575,7 @@ def has_cycle(self) -> bool: stack.append(s) visited.append(s) parent = -2 - indirect_parents = [] + indirect_parents: list[int] = [] ss = s on_the_way_back = False anticipating_nodes = set() From e283283d6fe9c0a967137a8278b6365a23e43677 Mon Sep 17 00:00:00 2001 From: Blas Date: Mon, 9 Jun 2025 22:52:29 -0400 Subject: [PATCH 8/8] Corrected typo in remove_pair docstring --- graphs/directed_and_undirected_weighted_graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphs/directed_and_undirected_weighted_graph.py b/graphs/directed_and_undirected_weighted_graph.py index 8ca6bbbdf236..5375ce73931c 100644 --- a/graphs/directed_and_undirected_weighted_graph.py +++ b/graphs/directed_and_undirected_weighted_graph.py @@ -316,7 +316,7 @@ def add_pair(self, u, v, w=1) -> None: # handles if the input does not exist def remove_pair(self, u, v) -> None: """ - Removes the edge between u and v in an unidirected graph, if it exists + Removes the edge between u and v in an undirected graph, if it exists >>> g = Graph() >>> g.add_pair(1,2)