diff --git a/wizard-work/beam/scc.py b/wizard-work/beam/scc.py new file mode 100644 index 0000000..ba49e05 --- /dev/null +++ b/wizard-work/beam/scc.py @@ -0,0 +1,55 @@ +"""Iterative Tarjan strongly connected components (no recursion limit).""" +from __future__ import annotations + +from typing import Dict, Iterable, List, Tuple + + +def scc(adj: Dict[int, Iterable[int]]) -> List[List[int]]: + index_counter = [0] + stack: List[int] = [] + on_stack: set = set() + indices: Dict[int, int] = {} + lowlink: Dict[int, int] = {} + result: List[List[int]] = [] + + for u in list(adj.keys()): + if u in indices: + continue + call_stack: List[Tuple[int, iter]] = [(u, iter(adj.get(u, ())))] + indices[u] = lowlink[u] = index_counter[0] + index_counter[0] += 1 + stack.append(u) + on_stack.add(u) + while call_stack: + v, it = call_stack[-1] + advanced = False + for w in it: + if w not in indices: + indices[w] = lowlink[w] = index_counter[0] + index_counter[0] += 1 + stack.append(w) + on_stack.add(w) + call_stack.append((w, iter(adj.get(w, ())))) + advanced = True + break + elif w in on_stack: + if indices[w] < lowlink[v]: + lowlink[v] = indices[w] + call_stack[-1] = (v, it) + if advanced: + continue + call_stack.pop() + if call_stack: + parent = call_stack[-1][0] + if lowlink[v] < lowlink[parent]: + lowlink[parent] = lowlink[v] + if lowlink[v] == indices[v]: + comp = [] + while True: + w = stack.pop() + on_stack.discard(w) + comp.append(w) + if w == v: + break + result.append(comp) + return result \ No newline at end of file diff --git a/wizard-work/beam/test_scc.py b/wizard-work/beam/test_scc.py new file mode 100644 index 0000000..e711b3b --- /dev/null +++ b/wizard-work/beam/test_scc.py @@ -0,0 +1,43 @@ +import sys +sys.path.insert(0, "/root/.hermes/wizard-outputs/beam") +from scc import scc + +def tarjan_recursive(adj): + idx=[0]; stack=[]; on=set(); indices={}; low={}; res=[] + import sys as _s + _s.setrecursionlimit(100000) + def strong(v): + indices[v]=low[v]=idx[0]; idx[0]+=1; stack.append(v); on.add(v) + for w in adj.get(v,()): + if w not in indices: + strong(w); low[v]=min(low[v],low[w]) + elif w in on: low[v]=min(low[v],indices[w]) + if low[v]==indices[v]: + comp=[] + while True: + w=stack.pop(); on.discard(w); comp.append(w) + if w==v: break + res.append(comp) + for u in list(adj): + if u not in indices: strong(u) + return res + +import random +random.seed(42) +for trial in range(200): + n=random.randint(1,40) + adj={i:[] for i in range(n)} + for i in range(n): + for j in range(n): + if random.random()<0.25: adj[i].append(j) + a=sorted(sorted(c) for c in scc(adj)) + b=sorted(sorted(c) for c in tarjan_recursive(adj)) + assert a==b, (a,b) +print("ALL 200 RANDOM TRIALS PASSED") +# big graph no recursion +N=200000 +adj={i:[i+1] for i in range(N-1)} +adj[N-1]=[0] +r=scc(adj) +assert len(r)==1 and len(r[0])==N +print("BIG CYCLE OK", len(r[0])) \ No newline at end of file