mirror of
https://github.com/python/cpython.git
synced 2025-12-08 06:10:17 +00:00
80 lines
2.6 KiB
Python
80 lines
2.6 KiB
Python
# Adapted from mypy (mypy/build.py) under the MIT license.
|
|
|
|
from collections.abc import Iterable, Iterator, Set
|
|
|
|
|
|
def strongly_connected_components(
|
|
vertices: Set[str], edges: dict[str, Set[str]]
|
|
) -> Iterator[Set[str]]:
|
|
"""Compute Strongly Connected Components of a directed graph.
|
|
|
|
Args:
|
|
vertices: the labels for the vertices
|
|
edges: for each vertex, gives the target vertices of its outgoing edges
|
|
|
|
Returns:
|
|
An iterator yielding strongly connected components, each
|
|
represented as a set of vertices. Each input vertex will occur
|
|
exactly once; vertices not part of a SCC are returned as
|
|
singleton sets.
|
|
|
|
From https://code.activestate.com/recipes/578507-strongly-connected-components-of-a-directed-graph/.
|
|
"""
|
|
identified: set[str] = set()
|
|
stack: list[str] = []
|
|
index: dict[str, int] = {}
|
|
boundaries: list[int] = []
|
|
|
|
def dfs(v: str) -> Iterator[set[str]]:
|
|
index[v] = len(stack)
|
|
stack.append(v)
|
|
boundaries.append(index[v])
|
|
|
|
for w in edges[v]:
|
|
if w not in index:
|
|
yield from dfs(w)
|
|
elif w not in identified:
|
|
while index[w] < boundaries[-1]:
|
|
boundaries.pop()
|
|
|
|
if boundaries[-1] == index[v]:
|
|
boundaries.pop()
|
|
scc = set(stack[index[v] :])
|
|
del stack[index[v] :]
|
|
identified.update(scc)
|
|
yield scc
|
|
|
|
for v in vertices:
|
|
if v not in index:
|
|
yield from dfs(v)
|
|
|
|
|
|
def find_cycles_in_scc(
|
|
graph: dict[str, Set[str]], scc: Set[str], start: str
|
|
) -> Iterable[list[str]]:
|
|
"""Find cycles in SCC emanating from start.
|
|
|
|
Yields lists of the form ['A', 'B', 'C', 'A'], which means there's
|
|
a path from A -> B -> C -> A. The first item is always the start
|
|
argument, but the last item may be another element, e.g. ['A',
|
|
'B', 'C', 'B'] means there's a path from A to B and there's a
|
|
cycle from B to C and back.
|
|
"""
|
|
# Basic input checks.
|
|
assert start in scc, (start, scc)
|
|
assert scc <= graph.keys(), scc - graph.keys()
|
|
|
|
# Reduce the graph to nodes in the SCC.
|
|
graph = {src: {dst for dst in dsts if dst in scc} for src, dsts in graph.items() if src in scc}
|
|
assert start in graph
|
|
|
|
# Recursive helper that yields cycles.
|
|
def dfs(node: str, path: list[str]) -> Iterator[list[str]]:
|
|
if node in path:
|
|
yield path + [node]
|
|
return
|
|
path = path + [node] # TODO: Make this not quadratic.
|
|
for child in graph[node]:
|
|
yield from dfs(child, path)
|
|
|
|
yield from dfs(start, [])
|