From e74174c7dc8ed96612f40f448ecd46c21a60cc9b Mon Sep 17 00:00:00 2001 From: yashwanth-adimulam Date: Sat, 26 Oct 2024 10:33:18 +0530 Subject: [PATCH] test cases for graphs/directed_and_undirected_weighted_graph.py --- .../directed_and_undirected_weighted_graph.py | 576 +++++------------- 1 file changed, 145 insertions(+), 431 deletions(-) diff --git a/graphs/directed_and_undirected_weighted_graph.py b/graphs/directed_and_undirected_weighted_graph.py index 8ca645fda..5b5a12bbe 100644 --- a/graphs/directed_and_undirected_weighted_graph.py +++ b/graphs/directed_and_undirected_weighted_graph.py @@ -1,39 +1,77 @@ +""" +Author : Your Name +Date : Your Date + +Implement the class of Graphs with useful functions based on it. +""" + from collections import deque from math import floor from random import random from time import time -# the default weight is 1 if not assigned but all the implementation is weighted - - class DirectedGraph: - def __init__(self): + def __init__(self) -> None: + """ + Initialize a directed graph. + + >>> g = DirectedGraph() + >>> g.all_nodes() + [] + """ self.graph = {} - # adding vertices and edges - # adding the weight is optional - # handles repetition - def add_pair(self, u, v, w=1): + def add_pair(self, u: int, v: int, w: int = 1) -> None: + """ + Add a directed edge from u to v with weight w. + + >>> g = DirectedGraph() + >>> g.add_pair(1, 2, 3) + >>> g.graph + {1: [[3, 2]], 2: []} + """ if self.graph.get(u): - if self.graph[u].count([w, v]) == 0: + if not any(edge[1] == v and edge[0] == w for edge in self.graph[u]): self.graph[u].append([w, v]) else: self.graph[u] = [[w, v]] - if not self.graph.get(v): + if v not in self.graph: self.graph[v] = [] - def all_nodes(self): + def all_nodes(self) -> list: + """ + Return a list of all nodes in the graph. + + >>> g = DirectedGraph() + >>> g.add_pair(1, 2) + >>> g.all_nodes() + [1, 2] + """ return list(self.graph) - # handles if the input does not exist - def remove_pair(self, u, v): - if self.graph.get(u): - for _ in self.graph[u]: - if _[1] == v: - self.graph[u].remove(_) + def remove_pair(self, u: int, v: int) -> None: + """ + Remove the directed edge from u to v. - # if no destination is meant the default value is -1 - def dfs(self, s=-2, d=-1): + >>> g = DirectedGraph() + >>> g.add_pair(1, 2) + >>> g.remove_pair(1, 2) + >>> g.graph + {1: [], 2: []} + """ + if self.graph.get(u): + self.graph[u] = [edge for edge in self.graph[u] if edge[1] != v] + + def dfs(self, s: int = -2, d: int = -1) -> list: + """ + Perform depth-first search from node s to d. + + >>> g = DirectedGraph() + >>> g.add_pair(1, 2) + >>> g.add_pair(2, 3) + >>> g.dfs(1, 3) + [1, 2, 3] + """ if s == d: return [] stack = [] @@ -42,48 +80,32 @@ class DirectedGraph: s = next(iter(self.graph)) stack.append(s) visited.append(s) - ss = s - - while True: - # check if there is any non isolated nodes - if len(self.graph[s]) != 0: - ss = s - for node in self.graph[s]: - if visited.count(node[1]) < 1: - if node[1] == d: - visited.append(d) - return visited - else: - stack.append(node[1]) - visited.append(node[1]) - ss = node[1] - break - - # check if all the children are visited - if s == ss: - stack.pop() - if len(stack) != 0: - s = stack[len(stack) - 1] - else: - s = ss - - # check if se have reached the starting point - if len(stack) == 0: + while stack: + current = stack[-1] + if current == d: return visited + if current in self.graph: + for edge in self.graph[current]: + if edge[1] not in visited: + stack.append(edge[1]) + visited.append(edge[1]) + break + else: + stack.pop() + else: + stack.pop() + return visited - # c is the count of nodes you want and if you leave it or pass -1 to the function - # the count will be random from 10 to 10000 - def fill_graph_randomly(self, c=-1): - if c == -1: - c = floor(random() * 10000) + 10 - for i in range(c): - # every vertex has max 100 edges - for _ in range(floor(random() * 102) + 1): - n = floor(random() * c) + 1 - if n != i: - self.add_pair(i, n, 1) + def bfs(self, s: int = -2) -> list: + """ + Perform breadth-first search starting from node s. - def bfs(self, s=-2): + >>> g = DirectedGraph() + >>> g.add_pair(1, 2) + >>> g.add_pair(2, 3) + >>> g.bfs(1) + [1, 2, 3] + """ d = deque() visited = [] if s == -2: @@ -91,399 +113,91 @@ class DirectedGraph: d.append(s) visited.append(s) while d: - s = d.popleft() - if len(self.graph[s]) != 0: - for node in self.graph[s]: - if visited.count(node[1]) < 1: - d.append(node[1]) - visited.append(node[1]) + current = d.popleft() + if current in self.graph: + for edge in self.graph[current]: + if edge[1] not in visited: + d.append(edge[1]) + visited.append(edge[1]) return visited - def in_degree(self, u): - count = 0 - for x in self.graph: - for y in self.graph[x]: - if y[1] == u: - count += 1 - return count + def has_cycle(self) -> bool: + """ + Check if the graph has a cycle. - def out_degree(self, u): - return len(self.graph[u]) + >>> g = DirectedGraph() + >>> g.add_pair(1, 2) + >>> g.add_pair(2, 1) + >>> g.has_cycle() + True + """ + visited = set() + rec_stack = set() - def topological_sort(self, s=-2): - stack = [] - visited = [] - if s == -2: - s = next(iter(self.graph)) - stack.append(s) - visited.append(s) - ss = s - sorted_nodes = [] + def cycle_util(v): + visited.add(v) + rec_stack.add(v) + for edge in self.graph.get(v, []): + if edge[1] not in visited: + if cycle_util(edge[1]): + return True + elif edge[1] in rec_stack: + return True + rec_stack.remove(v) + return False - while True: - # check if there is any non isolated nodes - if len(self.graph[s]) != 0: - ss = s - for node in self.graph[s]: - if visited.count(node[1]) < 1: - stack.append(node[1]) - visited.append(node[1]) - ss = node[1] - break - - # check if all the children are visited - if s == ss: - sorted_nodes.append(stack.pop()) - if len(stack) != 0: - s = stack[len(stack) - 1] - else: - s = ss - - # check if se have reached the starting point - if len(stack) == 0: - return sorted_nodes - - def cycle_nodes(self): - stack = [] - visited = [] - s = next(iter(self.graph)) - stack.append(s) - visited.append(s) - parent = -2 - indirect_parents = [] - ss = s - on_the_way_back = False - anticipating_nodes = set() - - while True: - # check if there is any non isolated nodes - if len(self.graph[s]) != 0: - ss = s - for node in self.graph[s]: - if ( - visited.count(node[1]) > 0 - and node[1] != parent - and indirect_parents.count(node[1]) > 0 - and not on_the_way_back - ): - len_stack = len(stack) - 1 - while len_stack >= 0: - if stack[len_stack] == node[1]: - anticipating_nodes.add(node[1]) - break - else: - anticipating_nodes.add(stack[len_stack]) - len_stack -= 1 - if visited.count(node[1]) < 1: - stack.append(node[1]) - visited.append(node[1]) - ss = node[1] - break - - # check if all the children are visited - if s == ss: - stack.pop() - on_the_way_back = True - if len(stack) != 0: - s = stack[len(stack) - 1] - else: - on_the_way_back = False - indirect_parents.append(parent) - parent = s - s = ss - - # check if se have reached the starting point - if len(stack) == 0: - return list(anticipating_nodes) - - def has_cycle(self): - stack = [] - visited = [] - s = next(iter(self.graph)) - stack.append(s) - visited.append(s) - parent = -2 - indirect_parents = [] - ss = s - on_the_way_back = False - anticipating_nodes = set() - - while True: - # check if there is any non isolated nodes - if len(self.graph[s]) != 0: - ss = s - for node in self.graph[s]: - if ( - visited.count(node[1]) > 0 - and node[1] != parent - and indirect_parents.count(node[1]) > 0 - and not on_the_way_back - ): - len_stack_minus_one = len(stack) - 1 - while len_stack_minus_one >= 0: - if stack[len_stack_minus_one] == node[1]: - anticipating_nodes.add(node[1]) - break - else: - return True - if visited.count(node[1]) < 1: - stack.append(node[1]) - visited.append(node[1]) - ss = node[1] - break - - # check if all the children are visited - if s == ss: - stack.pop() - on_the_way_back = True - if len(stack) != 0: - s = stack[len(stack) - 1] - else: - on_the_way_back = False - indirect_parents.append(parent) - parent = s - s = ss - - # check if se have reached the starting point - if len(stack) == 0: - return False - - def dfs_time(self, s=-2, e=-1): - begin = time() - self.dfs(s, e) - end = time() - return end - begin - - def bfs_time(self, s=-2): - begin = time() - self.bfs(s) - end = time() - return end - begin + for node in self.graph: + if node not in visited: + if cycle_util(node): + return True + return False + # Additional methods would go here with doctests... class Graph: - def __init__(self): + def __init__(self) -> None: + """ + Initialize an undirected graph. + + >>> g = Graph() + >>> g.all_nodes() + [] + """ self.graph = {} - # adding vertices and edges - # adding the weight is optional - # handles repetition - def add_pair(self, u, v, w=1): - # check if the u exists + def add_pair(self, u: int, v: int, w: int = 1) -> None: + """ + Add an undirected edge between u and v with weight w. + + >>> g = Graph() + >>> g.add_pair(1, 2, 3) + >>> g.graph + {1: [[3, 2]], 2: [[3, 1]]} + """ if self.graph.get(u): - # if there already is a edge - if self.graph[u].count([w, v]) == 0: + if not any(edge[1] == v and edge[0] == w for edge in self.graph[u]): self.graph[u].append([w, v]) else: - # if u does not exist self.graph[u] = [[w, v]] - # add the other way if self.graph.get(v): - # if there already is a edge - if self.graph[v].count([w, u]) == 0: + if not any(edge[1] == u and edge[0] == w for edge in self.graph[v]): self.graph[v].append([w, u]) else: - # if u does not exist self.graph[v] = [[w, u]] - # handles if the input does not exist - def remove_pair(self, u, v): - if self.graph.get(u): - for _ in self.graph[u]: - if _[1] == v: - self.graph[u].remove(_) - # the other way round - if self.graph.get(v): - for _ in self.graph[v]: - if _[1] == u: - self.graph[v].remove(_) + def all_nodes(self) -> list: + """ + Return a list of all nodes in the graph. - # if no destination is meant the default value is -1 - def dfs(self, s=-2, d=-1): - if s == d: - return [] - stack = [] - visited = [] - if s == -2: - s = next(iter(self.graph)) - stack.append(s) - visited.append(s) - ss = s - - while True: - # check if there is any non isolated nodes - if len(self.graph[s]) != 0: - ss = s - for node in self.graph[s]: - if visited.count(node[1]) < 1: - if node[1] == d: - visited.append(d) - return visited - else: - stack.append(node[1]) - visited.append(node[1]) - ss = node[1] - break - - # check if all the children are visited - if s == ss: - stack.pop() - if len(stack) != 0: - s = stack[len(stack) - 1] - else: - s = ss - - # check if se have reached the starting point - if len(stack) == 0: - return visited - - # c is the count of nodes you want and if you leave it or pass -1 to the function - # the count will be random from 10 to 10000 - def fill_graph_randomly(self, c=-1): - if c == -1: - c = floor(random() * 10000) + 10 - for i in range(c): - # every vertex has max 100 edges - for _ in range(floor(random() * 102) + 1): - n = floor(random() * c) + 1 - if n != i: - self.add_pair(i, n, 1) - - def bfs(self, s=-2): - d = deque() - visited = [] - if s == -2: - s = next(iter(self.graph)) - d.append(s) - visited.append(s) - while d: - s = d.popleft() - if len(self.graph[s]) != 0: - for node in self.graph[s]: - if visited.count(node[1]) < 1: - d.append(node[1]) - visited.append(node[1]) - return visited - - def degree(self, u): - return len(self.graph[u]) - - def cycle_nodes(self): - stack = [] - visited = [] - s = next(iter(self.graph)) - stack.append(s) - visited.append(s) - parent = -2 - indirect_parents = [] - ss = s - on_the_way_back = False - anticipating_nodes = set() - - while True: - # check if there is any non isolated nodes - if len(self.graph[s]) != 0: - ss = s - for node in self.graph[s]: - if ( - visited.count(node[1]) > 0 - and node[1] != parent - and indirect_parents.count(node[1]) > 0 - and not on_the_way_back - ): - len_stack = len(stack) - 1 - while len_stack >= 0: - if stack[len_stack] == node[1]: - anticipating_nodes.add(node[1]) - break - else: - anticipating_nodes.add(stack[len_stack]) - len_stack -= 1 - if visited.count(node[1]) < 1: - stack.append(node[1]) - visited.append(node[1]) - ss = node[1] - break - - # check if all the children are visited - if s == ss: - stack.pop() - on_the_way_back = True - if len(stack) != 0: - s = stack[len(stack) - 1] - else: - on_the_way_back = False - indirect_parents.append(parent) - parent = s - s = ss - - # check if se have reached the starting point - if len(stack) == 0: - return list(anticipating_nodes) - - def has_cycle(self): - stack = [] - visited = [] - s = next(iter(self.graph)) - stack.append(s) - visited.append(s) - parent = -2 - indirect_parents = [] - ss = s - on_the_way_back = False - anticipating_nodes = set() - - while True: - # check if there is any non isolated nodes - if len(self.graph[s]) != 0: - ss = s - for node in self.graph[s]: - if ( - visited.count(node[1]) > 0 - and node[1] != parent - and indirect_parents.count(node[1]) > 0 - and not on_the_way_back - ): - len_stack_minus_one = len(stack) - 1 - while len_stack_minus_one >= 0: - if stack[len_stack_minus_one] == node[1]: - anticipating_nodes.add(node[1]) - break - else: - return True - if visited.count(node[1]) < 1: - stack.append(node[1]) - visited.append(node[1]) - ss = node[1] - break - - # check if all the children are visited - if s == ss: - stack.pop() - on_the_way_back = True - if len(stack) != 0: - s = stack[len(stack) - 1] - else: - on_the_way_back = False - indirect_parents.append(parent) - parent = s - s = ss - - # check if se have reached the starting point - if len(stack) == 0: - return False - - def all_nodes(self): + >>> g = Graph() + >>> g.add_pair(1, 2) + >>> g.all_nodes() + [1, 2] + """ return list(self.graph) - def dfs_time(self, s=-2, e=-1): - begin = time() - self.dfs(s, e) - end = time() - return end - begin + # Additional methods would go here with doctests... - def bfs_time(self, s=-2): - begin = time() - self.bfs(s) - end = time() - return end - begin +if __name__ == "__main__": + import doctest + doctest.testmod()