Tipragot
628be439b8
Cela permet de ne pas avoir de problèmes de compatibilité car python est dans le git.
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}"
|