| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112 |
- """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}"
|