Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
allendowney
GitHub Repository: allendowney/cpython
Path: blob/main/Tools/peg_generator/pegen/sccutils.py
12 views
1
# Adapted from mypy (mypy/build.py) under the MIT license.
2
3
from typing import *
4
5
6
def strongly_connected_components(
7
vertices: AbstractSet[str], edges: Dict[str, AbstractSet[str]]
8
) -> Iterator[AbstractSet[str]]:
9
"""Compute Strongly Connected Components of a directed graph.
10
11
Args:
12
vertices: the labels for the vertices
13
edges: for each vertex, gives the target vertices of its outgoing edges
14
15
Returns:
16
An iterator yielding strongly connected components, each
17
represented as a set of vertices. Each input vertex will occur
18
exactly once; vertices not part of a SCC are returned as
19
singleton sets.
20
21
From http://code.activestate.com/recipes/578507/.
22
"""
23
identified: Set[str] = set()
24
stack: List[str] = []
25
index: Dict[str, int] = {}
26
boundaries: List[int] = []
27
28
def dfs(v: str) -> Iterator[Set[str]]:
29
index[v] = len(stack)
30
stack.append(v)
31
boundaries.append(index[v])
32
33
for w in edges[v]:
34
if w not in index:
35
yield from dfs(w)
36
elif w not in identified:
37
while index[w] < boundaries[-1]:
38
boundaries.pop()
39
40
if boundaries[-1] == index[v]:
41
boundaries.pop()
42
scc = set(stack[index[v] :])
43
del stack[index[v] :]
44
identified.update(scc)
45
yield scc
46
47
for v in vertices:
48
if v not in index:
49
yield from dfs(v)
50
51
52
def topsort(
53
data: Dict[AbstractSet[str], Set[AbstractSet[str]]]
54
) -> Iterable[AbstractSet[AbstractSet[str]]]:
55
"""Topological sort.
56
57
Args:
58
data: A map from SCCs (represented as frozen sets of strings) to
59
sets of SCCs, its dependencies. NOTE: This data structure
60
is modified in place -- for normalization purposes,
61
self-dependencies are removed and entries representing
62
orphans are added.
63
64
Returns:
65
An iterator yielding sets of SCCs that have an equivalent
66
ordering. NOTE: The algorithm doesn't care about the internal
67
structure of SCCs.
68
69
Example:
70
Suppose the input has the following structure:
71
72
{A: {B, C}, B: {D}, C: {D}}
73
74
This is normalized to:
75
76
{A: {B, C}, B: {D}, C: {D}, D: {}}
77
78
The algorithm will yield the following values:
79
80
{D}
81
{B, C}
82
{A}
83
84
From http://code.activestate.com/recipes/577413/.
85
"""
86
# TODO: Use a faster algorithm?
87
for k, v in data.items():
88
v.discard(k) # Ignore self dependencies.
89
for item in set.union(*data.values()) - set(data.keys()):
90
data[item] = set()
91
while True:
92
ready = {item for item, dep in data.items() if not dep}
93
if not ready:
94
break
95
yield ready
96
data = {item: (dep - ready) for item, dep in data.items() if item not in ready}
97
assert not data, "A cyclic dependency exists amongst %r" % data
98
99
100
def find_cycles_in_scc(
101
graph: Dict[str, AbstractSet[str]], scc: AbstractSet[str], start: str
102
) -> Iterable[List[str]]:
103
"""Find cycles in SCC emanating from start.
104
105
Yields lists of the form ['A', 'B', 'C', 'A'], which means there's
106
a path from A -> B -> C -> A. The first item is always the start
107
argument, but the last item may be another element, e.g. ['A',
108
'B', 'C', 'B'] means there's a path from A to B and there's a
109
cycle from B to C and back.
110
"""
111
# Basic input checks.
112
assert start in scc, (start, scc)
113
assert scc <= graph.keys(), scc - graph.keys()
114
115
# Reduce the graph to nodes in the SCC.
116
graph = {src: {dst for dst in dsts if dst in scc} for src, dsts in graph.items() if src in scc}
117
assert start in graph
118
119
# Recursive helper that yields cycles.
120
def dfs(node: str, path: List[str]) -> Iterator[List[str]]:
121
if node in path:
122
yield path + [node]
123
return
124
path = path + [node] # TODO: Make this not quadratic.
125
for child in graph[node]:
126
yield from dfs(child, path)
127
128
yield from dfs(start, [])
129
130