-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgraph.py
64 lines (50 loc) · 1.46 KB
/
graph.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
from enum import Enum
import typing as tp
T = tp.TypeVar('T')
class Color(Enum):
NOT_VISITED = 0,
VISITING = 1,
VISITED = 2
class CycleFound(Exception):
pass
class Graph:
def __init__(self, graph: tp.Dict[T, tp.Iterable[T]]):
self.graph = graph
self.state = {}
def has_cycle(self) -> bool:
self.state = {v: Color.NOT_VISITED for v in self.graph}
for v in self.graph:
try:
self._dfs(v)
except CycleFound:
return True
return False
def find_reachables(self) -> tp.Dict[T, tp.Set[T]]:
def dfs(v: T) -> None:
visited.add(v)
for u in self.graph[v]:
if u not in visited:
dfs(u)
result = {}
for v in self.graph:
visited = set()
dfs(v)
result[v] = visited
return result
def _dfs(self, v):
if self.state[v] == Color.VISITED:
return
if self.state[v] == Color.VISITING:
raise CycleFound()
self.state[v] = Color.VISITING
for next_v in self.graph[v]:
self._dfs(next_v)
self.state[v] = Color.VISITED
def test_has_cycle():
loop = Graph({1: [1]})
graph = Graph({1: [2, 3], 2: [], 3: [4], 4: [2]})
assert not graph.has_cycle()
graph = Graph({2: [3, 4], 1: [3], 3: [4], 4: [1]})
assert graph.has_cycle()
if __name__ == "__main__":
test_has_cycle()