UNPKG

joshkaposh-graph

Version:

A collection of general purpose graph data structures and traversal algorithms

204 lines (168 loc) 6.33 kB
import { done, iter, Iterator, range } from "joshkaposh-iterator"; import { Option } from "joshkaposh-option"; import { DfsPostOrder, GraphBase, IntoNeighbors, Visitable } from "../visit"; import { assert } from "joshkaposh-iterator/src/util"; export class Dominators<N> { #root: N; #dominators: Map<N, N>; constructor(root: N, dominators: Map<N, N>) { this.#root = root; this.#dominators = dominators } root(): N { return this.#root; } immediate_dominator(node: N): Option<N> { if (node === this.#root) { return } return this.#dominators.get(node)! } strict_dominators(node: N): Option<DominatorsIter<N>> { if (this.#dominators.has(node)) { return new DominatorsIter(this, this.immediate_dominator(node)!) } return } dominators(node: N) { if (this.#dominators.has(node)) { return new DominatorsIter(this, node) } return } immediately_dominated_by(node: N): DominatedByIter<N> { return new DominatedByIter(iter(this.#dominators), node) } } export class DominatorsIter<N> extends Iterator<N> { #dominators: Dominators<N>; #node: Option<N>; constructor(dominators: Dominators<N>, node: N) { super() this.#dominators = dominators; this.#node = node; } into_iter() { return this; } #take(): Option<N> { const n = this.#node; this.#node = undefined; return n; } next(): IteratorResult<N> { const next = this.#take() if (next !== undefined) { this.#node = this.#dominators.immediate_dominator(next!); return { done: false, value: next as N } } return done(); } } export class DominatedByIter<N> extends Iterator<N> { #iter: Iterator<[N, N]>; #node: N constructor(iter: Iterator<[N, N]>, node: N) { super() this.#iter = iter; this.#node = node; } into_iter() { return this; } next(): IteratorResult<N> { for (const next of this.#iter) { if (next[1] === this.#node) { return { done: false, value: next[0] } } } return done() } size_hint(): [number, Option<number>] { const [_, upper] = this.#iter.size_hint(); return [0, upper]; } } const UNDEFINED = Number.MAX_SAFE_INTEGER; export function simple_fast<G extends GraphBase<any, any> & IntoNeighbors & Visitable>(graph: G, root: G['NodeId']): Dominators<G['NodeId']> { const [post_order, predecessor_sets] = simple_fast_post_order(graph, root); const length = post_order.length; assert(length > 0); assert(post_order[length - 1] === root); const node_to_post_order_idx = iter(post_order) .enumerate() .map(([idx, node]) => [node, idx]) .collect(Map) as Map<G['NodeId'], number>; const idx_to_predecessor_arr = predecessors_sets_to_idx_arrs(post_order, node_to_post_order_idx, predecessor_sets); const dominators = Array.from({ length }, () => UNDEFINED); dominators[length - 1] = length - 1; let changed = true; while (changed) { changed = false; for (const idx of range(0, length - 1).rev()) { assert(post_order[idx] !== root) let new_idom_idx; const predecessors = iter(idx_to_predecessor_arr[idx]).filter(p => dominators[p] !== UNDEFINED) const n = predecessors.next() if (n.done) { throw new Error("Because the root is initialized to dominate itself, and is the \ first node in every path, there must exist a predecessor to this \ node that also has a dominator") } new_idom_idx = n.value; new_idom_idx = predecessors.fold(new_idom_idx, (new_idx, predecessor_idx) => intersect(dominators, new_idx, predecessor_idx)) assert(new_idom_idx < length); if (new_idom_idx !== dominators[idx]) { dominators[idx] = new_idom_idx; changed = true; } } } // All done! Translate the indices to G["NodeId"] assert(!iter(dominators).any(dom => dom === UNDEFINED)); return new Dominators( root, iter(dominators) .enumerate() .map(([idx, dom_idx]) => [post_order[idx], post_order[dom_idx]]) .collect(Map) ) } function intersect(dominators: number[], finger1: number, finger2: number): number { while (true) { if (finger1 < finger2) { finger1 = dominators[finger1] } else if (finger1 > finger2) { finger2 = dominators[finger2]; } else { return finger1 } } } function predecessors_sets_to_idx_arrs<N>(post_order: N[], node_to_post_order_idx: Map<N, number>, predecessor_sets: Map<N, Set<N>>): Array<number[]> { return iter(post_order) .map(node => { const predecessors = predecessor_sets.get(node)! predecessor_sets.delete(node); if (predecessors) { return iter(predecessor_sets.keys()).map(p => node_to_post_order_idx.get(p)!) } return iter([]) }).collect() as unknown as Array<number[]> } type PredecessorSets<NodeId> = Map<NodeId, Set<NodeId>> function simple_fast_post_order<G extends GraphBase<any, any> & IntoNeighbors & Visitable>(graph: G, root: G['NodeId']): [G['NodeId'][], PredecessorSets<G['NodeId']>] { const post_order = []; const predecessor_sets = new Map(); for (const node of new DfsPostOrder(graph, root).iter(graph)) { post_order.push(node); for (const successor of graph.neighbors(node)) { if (!predecessor_sets.has(successor)) { predecessor_sets.set(successor, new Set([node])) } else { predecessor_sets.get(successor)!.add(node) } } } return [post_order, predecessor_sets] }