|
1 | | -from abc import ABC, abstractmethod |
2 | | -from collections import defaultdict |
3 | | -from pprint import PrettyPrinter |
4 | | -from typing import List, Set, Union, Generic, TypeVar |
5 | | -from datastructures.stacks import Stack |
6 | 1 | from .vertex import Vertex |
7 | | -from .edge import Edge |
8 | | - |
9 | | -T = TypeVar("T") |
10 | | - |
11 | | - |
12 | | -class Graph(ABC, Generic[T]): |
13 | | - """ |
14 | | - Represents a Graph Data structure |
15 | | - """ |
16 | | - |
17 | | - def __init__(self, edge_list: List[Edge] = None): |
18 | | - if edge_list is None: |
19 | | - edge_list = [] |
20 | | - self.edge_list = edge_list |
21 | | - self.adjacency_list = defaultdict(List[Vertex]) |
22 | | - self.__construct_adjacency_list() |
23 | | - self.nodes = [] |
24 | | - self.node_count = len(self.nodes) |
25 | | - |
26 | | - def add(self, node_one: Vertex, node_two: Vertex): |
27 | | - """ |
28 | | - Adds a connection between node_one and node_two |
29 | | - """ |
30 | | - node_one.neighbours.append(node_two) |
31 | | - node_two.neighbours.append(node_one) |
32 | | - edge = Edge(source=node_one, destination=node_two) |
33 | | - self.edge_list.append(edge) |
34 | | - self.adjacency_list[node_one].append(node_two) |
35 | | - self.adjacency_list[node_two].append(node_one) |
36 | | - self.nodes.append(node_one) |
37 | | - self.nodes.append(node_two) |
38 | | - |
39 | | - def __construct_adjacency_list(self): |
40 | | - """ |
41 | | - Construct adjacency list |
42 | | - """ |
43 | | - for edge in self.edge_list: |
44 | | - self.adjacency_list[edge.source].append(edge.destination) |
45 | | - |
46 | | - @abstractmethod |
47 | | - def bfs_from_root_to_target(self, root: Vertex, target: Vertex) -> Set[Vertex]: |
48 | | - """ |
49 | | - Given the root node to traverse and a target node, returns the BFS result of this Graph from the root node to |
50 | | - the target node |
51 | | - """ |
52 | | - raise NotImplementedError("Not yet implemented") |
53 | | - |
54 | | - @abstractmethod |
55 | | - def bfs_from_node(self, source: Vertex) -> Set[Vertex]: |
56 | | - """ |
57 | | - Given the source to traverse, returns the BFS result of this Graph from the source node |
58 | | - """ |
59 | | - raise NotImplementedError("Not yet implemented") |
60 | | - |
61 | | - def topological_sorted_order(self) -> List[Vertex]: |
62 | | - """ |
63 | | - Returns the topological sorted order of the Graph |
64 | | - """ |
65 | | - # These static variables are used to perform DFS recursion |
66 | | - # white nodes depict nodes that have not been visited yet |
67 | | - # gray nodes depict ongoing recursion |
68 | | - # black nodes depict recursion is complete |
69 | | - # An edge leading to a BLACK node is not a "cycle" |
70 | | - white = 1 |
71 | | - gray = 2 |
72 | | - black = 3 |
73 | | - |
74 | | - # Nothing to do here |
75 | | - if self.node_count == 0: |
76 | | - return [] |
77 | | - |
78 | | - is_possible = True |
79 | | - stack = Stack() |
80 | | - |
81 | | - # By default all nodes are WHITE |
82 | | - visited_nodes = {node: white for node in range(self.node_count)} |
83 | | - |
84 | | - def dfs(node: Vertex): |
85 | | - nonlocal is_possible |
86 | | - |
87 | | - # Don't recurse further if we found a cycle already |
88 | | - if not is_possible: |
89 | | - return |
90 | | - |
91 | | - # start recursion |
92 | | - visited_nodes[node] = gray |
93 | | - |
94 | | - # Traverse on neighbouring nodes/vertices |
95 | | - if node in self.adjacency_list: |
96 | | - for neighbour in self.adjacency_list[node]: |
97 | | - if visited_nodes[neighbour] == white: |
98 | | - dfs(node) |
99 | | - elif visited_nodes[node] == gray: |
100 | | - # An Edge to a Gray vertex/node represents a cycle |
101 | | - is_possible = False |
102 | | - |
103 | | - # Recursion ends. We mark if as BLACK |
104 | | - visited_nodes[node] = black |
105 | | - stack.push(node) |
106 | | - |
107 | | - for node in self.nodes: |
108 | | - # if the node is unprocessed, then call DFS on it |
109 | | - if visited_nodes[node] == white: |
110 | | - dfs(node) |
111 | | - |
112 | | - return list(stack.stack) if is_possible else [] |
113 | | - |
114 | | - def print(self): |
115 | | - pretty_print = PrettyPrinter() |
116 | | - pretty_print.pprint(self.adjacency_list) |
117 | | - |
118 | | - def remove(self, node: Vertex) -> None: |
119 | | - """ |
120 | | - Removes all references to a node |
121 | | - :param node |
122 | | - """ |
123 | | - for _, cxns in self.adjacency_list.items(): |
124 | | - try: |
125 | | - cxns.remove(node) |
126 | | - except KeyError: |
127 | | - pass |
128 | | - |
129 | | - try: |
130 | | - del self.adjacency_list[node] |
131 | | - except KeyError: |
132 | | - pass |
133 | | - |
134 | | - def is_connected(self, node_one: Vertex, node_two: Vertex) -> bool: |
135 | | - return ( |
136 | | - node_one in self.adjacency_list |
137 | | - and node_two in self.adjacency_list[node_two] |
138 | | - ) |
139 | | - |
140 | | - def find_path( |
141 | | - self, node_one: Vertex, node_two: Vertex, path=None |
142 | | - ) -> Union[List, None]: |
143 | | - """ |
144 | | - Find any path between node_one and node_two. May not be the shortest path |
145 | | - :param node_one |
146 | | - :param node_two |
147 | | - :param path |
148 | | - """ |
149 | | - |
150 | | - if path is None: |
151 | | - path = [] |
152 | | - path = [path] + [node_one] |
153 | | - |
154 | | - if node_one.data == node_two.data: |
155 | | - return path |
156 | | - |
157 | | - if node_one.data not in self.adjacency_list: |
158 | | - return None |
159 | | - |
160 | | - for node in self.adjacency_list[node_one]: |
161 | | - if node.data not in path: |
162 | | - new_path = self.find_path(node, node_two, path) |
163 | | - |
164 | | - if new_path: |
165 | | - return new_path |
166 | | - |
167 | | - return None |
168 | | - |
169 | | - def find_all_paths( |
170 | | - self, node_one: Vertex, node_two: Vertex, path: List = None |
171 | | - ) -> list: |
172 | | - """ |
173 | | - Finds all paths between node_one and node_two, where node_one is the start & node_two is the end |
174 | | - :param node_one Graph Node |
175 | | - :param node_two Graph Node |
176 | | - :param path |
177 | | - """ |
178 | | - if path is None: |
179 | | - path = [] |
180 | | - path = path + [node_one] |
181 | | - |
182 | | - if node_one.data == node_two.data: |
183 | | - return [path] |
184 | | - |
185 | | - if node_one.data not in self.adjacency_list: |
186 | | - return [] |
187 | | - |
188 | | - paths = [] |
189 | | - |
190 | | - for node in self.adjacency_list[node_one.data]: |
191 | | - if node not in path: |
192 | | - newpaths = self.find_all_paths(Vertex(node), node_two, path) |
193 | | - for newpath in newpaths: |
194 | | - paths.append(newpath) |
195 | | - |
196 | | - return paths |
197 | | - |
198 | | - def find_shortest_path( |
199 | | - self, node_one: Vertex, node_two: Vertex, path: List = None |
200 | | - ) -> Union[List, None]: |
201 | | - """ |
202 | | - Finds the shortest path between 2 nodes in the graph |
203 | | - """ |
204 | | - if path is None: |
205 | | - path = [] |
206 | | - |
207 | | - path = path + [node_one] |
208 | | - |
209 | | - if node_one.data == node_two.data: |
210 | | - return path |
211 | | - |
212 | | - if node_one.data not in self.adjacency_list: |
213 | | - return None |
214 | | - |
215 | | - shortest = None |
216 | | - |
217 | | - for node in self.adjacency_list[node_one]: |
218 | | - if node.data not in path: |
219 | | - newpath = self.find_shortest_path(node, node_two, path) |
220 | | - if newpath: |
221 | | - if not shortest or len(newpath) < len(shortest): |
222 | | - shortest = newpath |
223 | | - |
224 | | - return shortest |
225 | | - |
226 | | - def __str__(self): |
227 | | - """ |
228 | | - Return string representation of this Graph |
229 | | - """ |
230 | | - return f"Graph: {self.adjacency_list}" |
| 2 | +from .edge import Edge, EdgeType, DirectedEdge, UndirectedEdge, SelfEdge, HyperEdge |
| 3 | +from .graph import Graph |
| 4 | + |
| 5 | +__all__ = [ |
| 6 | + "Edge", |
| 7 | + "EdgeType", |
| 8 | + "Vertex", |
| 9 | + "Graph", |
| 10 | + "UndirectedEdge", |
| 11 | + "DirectedEdge", |
| 12 | + "SelfEdge", |
| 13 | + "HyperEdge", |
| 14 | +] |
0 commit comments