113 lines
3.3 KiB
Python
113 lines
3.3 KiB
Python
|
"""Helpers for manipulations with graphs."""
|
||
|
|
||
|
from __future__ import annotations
|
||
|
|
||
|
from typing import AbstractSet, Iterable, Iterator, TypeVar
|
||
|
|
||
|
T = TypeVar("T")
|
||
|
|
||
|
|
||
|
def strongly_connected_components(
|
||
|
vertices: AbstractSet[T], edges: dict[T, list[T]]
|
||
|
) -> Iterator[set[T]]:
|
||
|
"""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/.
|
||
|
"""
|
||
|
identified: set[T] = set()
|
||
|
stack: list[T] = []
|
||
|
index: dict[T, int] = {}
|
||
|
boundaries: list[int] = []
|
||
|
|
||
|
def dfs(v: T) -> Iterator[set[T]]:
|
||
|
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 prepare_sccs(
|
||
|
sccs: list[set[T]], edges: dict[T, list[T]]
|
||
|
) -> dict[AbstractSet[T], set[AbstractSet[T]]]:
|
||
|
"""Use original edges to organize SCCs in a graph by dependencies between them."""
|
||
|
sccsmap = {v: frozenset(scc) for scc in sccs for v in scc}
|
||
|
data: dict[AbstractSet[T], set[AbstractSet[T]]] = {}
|
||
|
for scc in sccs:
|
||
|
deps: set[AbstractSet[T]] = set()
|
||
|
for v in scc:
|
||
|
deps.update(sccsmap[x] for x in edges[v])
|
||
|
data[frozenset(scc)] = deps
|
||
|
return data
|
||
|
|
||
|
|
||
|
def topsort(data: dict[T, set[T]]) -> Iterable[set[T]]:
|
||
|
"""Topological sort.
|
||
|
|
||
|
Args:
|
||
|
data: A map from vertices to all vertices that it has an edge
|
||
|
connecting it to. NOTE: This data structure
|
||
|
is modified in place -- for normalization purposes,
|
||
|
self-dependencies are removed and entries representing
|
||
|
orphans are added.
|
||
|
|
||
|
Returns:
|
||
|
An iterator yielding sets of vertices that have an equivalent
|
||
|
ordering.
|
||
|
|
||
|
Example:
|
||
|
Suppose the input has the following structure:
|
||
|
|
||
|
{A: {B, C}, B: {D}, C: {D}}
|
||
|
|
||
|
This is normalized to:
|
||
|
|
||
|
{A: {B, C}, B: {D}, C: {D}, D: {}}
|
||
|
|
||
|
The algorithm will yield the following values:
|
||
|
|
||
|
{D}
|
||
|
{B, C}
|
||
|
{A}
|
||
|
|
||
|
From https://code.activestate.com/recipes/577413/.
|
||
|
"""
|
||
|
# TODO: Use a faster algorithm?
|
||
|
for k, v in data.items():
|
||
|
v.discard(k) # Ignore self dependencies.
|
||
|
for item in set.union(*data.values()) - set(data.keys()):
|
||
|
data[item] = set()
|
||
|
while True:
|
||
|
ready = {item for item, dep in data.items() if not dep}
|
||
|
if not ready:
|
||
|
break
|
||
|
yield ready
|
||
|
data = {item: (dep - ready) for item, dep in data.items() if item not in ready}
|
||
|
assert not data, f"A cyclic dependency exists amongst {data!r}"
|