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