17db96d56Sopenharmony_ci# Adapted from mypy (mypy/build.py) under the MIT license.
27db96d56Sopenharmony_ci
37db96d56Sopenharmony_cifrom typing import *
47db96d56Sopenharmony_ci
57db96d56Sopenharmony_ci
67db96d56Sopenharmony_cidef strongly_connected_components(
77db96d56Sopenharmony_ci    vertices: AbstractSet[str], edges: Dict[str, AbstractSet[str]]
87db96d56Sopenharmony_ci) -> Iterator[AbstractSet[str]]:
97db96d56Sopenharmony_ci    """Compute Strongly Connected Components of a directed graph.
107db96d56Sopenharmony_ci
117db96d56Sopenharmony_ci    Args:
127db96d56Sopenharmony_ci      vertices: the labels for the vertices
137db96d56Sopenharmony_ci      edges: for each vertex, gives the target vertices of its outgoing edges
147db96d56Sopenharmony_ci
157db96d56Sopenharmony_ci    Returns:
167db96d56Sopenharmony_ci      An iterator yielding strongly connected components, each
177db96d56Sopenharmony_ci      represented as a set of vertices.  Each input vertex will occur
187db96d56Sopenharmony_ci      exactly once; vertices not part of a SCC are returned as
197db96d56Sopenharmony_ci      singleton sets.
207db96d56Sopenharmony_ci
217db96d56Sopenharmony_ci    From http://code.activestate.com/recipes/578507/.
227db96d56Sopenharmony_ci    """
237db96d56Sopenharmony_ci    identified: Set[str] = set()
247db96d56Sopenharmony_ci    stack: List[str] = []
257db96d56Sopenharmony_ci    index: Dict[str, int] = {}
267db96d56Sopenharmony_ci    boundaries: List[int] = []
277db96d56Sopenharmony_ci
287db96d56Sopenharmony_ci    def dfs(v: str) -> Iterator[Set[str]]:
297db96d56Sopenharmony_ci        index[v] = len(stack)
307db96d56Sopenharmony_ci        stack.append(v)
317db96d56Sopenharmony_ci        boundaries.append(index[v])
327db96d56Sopenharmony_ci
337db96d56Sopenharmony_ci        for w in edges[v]:
347db96d56Sopenharmony_ci            if w not in index:
357db96d56Sopenharmony_ci                yield from dfs(w)
367db96d56Sopenharmony_ci            elif w not in identified:
377db96d56Sopenharmony_ci                while index[w] < boundaries[-1]:
387db96d56Sopenharmony_ci                    boundaries.pop()
397db96d56Sopenharmony_ci
407db96d56Sopenharmony_ci        if boundaries[-1] == index[v]:
417db96d56Sopenharmony_ci            boundaries.pop()
427db96d56Sopenharmony_ci            scc = set(stack[index[v] :])
437db96d56Sopenharmony_ci            del stack[index[v] :]
447db96d56Sopenharmony_ci            identified.update(scc)
457db96d56Sopenharmony_ci            yield scc
467db96d56Sopenharmony_ci
477db96d56Sopenharmony_ci    for v in vertices:
487db96d56Sopenharmony_ci        if v not in index:
497db96d56Sopenharmony_ci            yield from dfs(v)
507db96d56Sopenharmony_ci
517db96d56Sopenharmony_ci
527db96d56Sopenharmony_cidef topsort(
537db96d56Sopenharmony_ci    data: Dict[AbstractSet[str], Set[AbstractSet[str]]]
547db96d56Sopenharmony_ci) -> Iterable[AbstractSet[AbstractSet[str]]]:
557db96d56Sopenharmony_ci    """Topological sort.
567db96d56Sopenharmony_ci
577db96d56Sopenharmony_ci    Args:
587db96d56Sopenharmony_ci      data: A map from SCCs (represented as frozen sets of strings) to
597db96d56Sopenharmony_ci            sets of SCCs, its dependencies.  NOTE: This data structure
607db96d56Sopenharmony_ci            is modified in place -- for normalization purposes,
617db96d56Sopenharmony_ci            self-dependencies are removed and entries representing
627db96d56Sopenharmony_ci            orphans are added.
637db96d56Sopenharmony_ci
647db96d56Sopenharmony_ci    Returns:
657db96d56Sopenharmony_ci      An iterator yielding sets of SCCs that have an equivalent
667db96d56Sopenharmony_ci      ordering.  NOTE: The algorithm doesn't care about the internal
677db96d56Sopenharmony_ci      structure of SCCs.
687db96d56Sopenharmony_ci
697db96d56Sopenharmony_ci    Example:
707db96d56Sopenharmony_ci      Suppose the input has the following structure:
717db96d56Sopenharmony_ci
727db96d56Sopenharmony_ci        {A: {B, C}, B: {D}, C: {D}}
737db96d56Sopenharmony_ci
747db96d56Sopenharmony_ci      This is normalized to:
757db96d56Sopenharmony_ci
767db96d56Sopenharmony_ci        {A: {B, C}, B: {D}, C: {D}, D: {}}
777db96d56Sopenharmony_ci
787db96d56Sopenharmony_ci      The algorithm will yield the following values:
797db96d56Sopenharmony_ci
807db96d56Sopenharmony_ci        {D}
817db96d56Sopenharmony_ci        {B, C}
827db96d56Sopenharmony_ci        {A}
837db96d56Sopenharmony_ci
847db96d56Sopenharmony_ci    From http://code.activestate.com/recipes/577413/.
857db96d56Sopenharmony_ci    """
867db96d56Sopenharmony_ci    # TODO: Use a faster algorithm?
877db96d56Sopenharmony_ci    for k, v in data.items():
887db96d56Sopenharmony_ci        v.discard(k)  # Ignore self dependencies.
897db96d56Sopenharmony_ci    for item in set.union(*data.values()) - set(data.keys()):
907db96d56Sopenharmony_ci        data[item] = set()
917db96d56Sopenharmony_ci    while True:
927db96d56Sopenharmony_ci        ready = {item for item, dep in data.items() if not dep}
937db96d56Sopenharmony_ci        if not ready:
947db96d56Sopenharmony_ci            break
957db96d56Sopenharmony_ci        yield ready
967db96d56Sopenharmony_ci        data = {item: (dep - ready) for item, dep in data.items() if item not in ready}
977db96d56Sopenharmony_ci    assert not data, "A cyclic dependency exists amongst %r" % data
987db96d56Sopenharmony_ci
997db96d56Sopenharmony_ci
1007db96d56Sopenharmony_cidef find_cycles_in_scc(
1017db96d56Sopenharmony_ci    graph: Dict[str, AbstractSet[str]], scc: AbstractSet[str], start: str
1027db96d56Sopenharmony_ci) -> Iterable[List[str]]:
1037db96d56Sopenharmony_ci    """Find cycles in SCC emanating from start.
1047db96d56Sopenharmony_ci
1057db96d56Sopenharmony_ci    Yields lists of the form ['A', 'B', 'C', 'A'], which means there's
1067db96d56Sopenharmony_ci    a path from A -> B -> C -> A.  The first item is always the start
1077db96d56Sopenharmony_ci    argument, but the last item may be another element, e.g.  ['A',
1087db96d56Sopenharmony_ci    'B', 'C', 'B'] means there's a path from A to B and there's a
1097db96d56Sopenharmony_ci    cycle from B to C and back.
1107db96d56Sopenharmony_ci    """
1117db96d56Sopenharmony_ci    # Basic input checks.
1127db96d56Sopenharmony_ci    assert start in scc, (start, scc)
1137db96d56Sopenharmony_ci    assert scc <= graph.keys(), scc - graph.keys()
1147db96d56Sopenharmony_ci
1157db96d56Sopenharmony_ci    # Reduce the graph to nodes in the SCC.
1167db96d56Sopenharmony_ci    graph = {src: {dst for dst in dsts if dst in scc} for src, dsts in graph.items() if src in scc}
1177db96d56Sopenharmony_ci    assert start in graph
1187db96d56Sopenharmony_ci
1197db96d56Sopenharmony_ci    # Recursive helper that yields cycles.
1207db96d56Sopenharmony_ci    def dfs(node: str, path: List[str]) -> Iterator[List[str]]:
1217db96d56Sopenharmony_ci        if node in path:
1227db96d56Sopenharmony_ci            yield path + [node]
1237db96d56Sopenharmony_ci            return
1247db96d56Sopenharmony_ci        path = path + [node]  # TODO: Make this not quadratic.
1257db96d56Sopenharmony_ci        for child in graph[node]:
1267db96d56Sopenharmony_ci            yield from dfs(child, path)
1277db96d56Sopenharmony_ci
1287db96d56Sopenharmony_ci    yield from dfs(start, [])
129