Viewing file: sccutils.py (3.96 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
# Adapted from mypy (mypy/build.py) under the MIT license.
from typing import *
def strongly_connected_components( vertices: AbstractSet[str], edges: Dict[str, AbstractSet[str]] ) -> Iterator[AbstractSet[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 http://code.activestate.com/recipes/578507/. """ 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 topsort( data: Dict[AbstractSet[str], Set[AbstractSet[str]]] ) -> Iterable[AbstractSet[AbstractSet[str]]]: """Topological sort.
Args: data: A map from SCCs (represented as frozen sets of strings) to sets of SCCs, its dependencies. 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 SCCs that have an equivalent ordering. NOTE: The algorithm doesn't care about the internal structure of SCCs.
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 http://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, "A cyclic dependency exists amongst %r" % data
def find_cycles_in_scc( graph: Dict[str, AbstractSet[str]], scc: AbstractSet[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, [])
|