UNPKG

joshkaposh-graph

Version:

A collection of general purpose graph data structures and traversal algorithms

835 lines (695 loc) 26.9 kB
import { done, DoubleEndedIterator, iter, Iterator } from "joshkaposh-iterator"; import { is_some, type Option } from "joshkaposh-option"; import { FixedBitSet } from "fixed-bit-set"; import { assert } from "joshkaposh-iterator/src/util"; import { type GraphIx, EdgeReference, Graph, swap_pair } from "."; import { type Node, type Edge, createEdge, Directed, DIRECTIONS, EdgeType, index_twice, Direction, Outgoing, Incoming, Undirected } from "./shared"; import { swap } from "../array-helpers"; import { enumerate } from "../util"; import { type EdgeId, type NodeId, VisitMap, EdgeRef, NodeRef, VisitorFbs, type GraphImpl, Visitable } from "../visit"; export function DiStableGraph<N, E, Ix extends GraphIx = 32>(size: Ix = 32 as Ix): StableGraph<N, E, Directed, Ix> { return StableGraph.directed(size) } export function UnStableGraph<N, E, Ix extends GraphIx = 32>(size: Ix = 32 as Ix): StableGraph<N, E, Undirected, Ix> { return StableGraph.undirected(size) } export class StableGraph<N, E, Ty extends EdgeType, Ix extends GraphIx = 32> implements GraphImpl<number, number, N, E>, Visitable<number> { readonly NodeEnd: number; readonly EdgeEnd: number; NodeId!: number; EdgeId!: number; NodeWeight!: N; EdgeWeight!: E; #g: Graph<Option<N>, Option<E>, Ty, Ix>; #ty: Ty; #ix: Ix; #node_count: number; #edge_count: number; #free_node: number; #free_edge: number; get __inner() { return this.#g; } constructor(ty: Ty = Directed as Ty, ix: Ix = 32 as Ix, graph: Graph<Option<N>, Option<E>, Ty, Ix> = Graph.with_capacity(ty, ix, 0, 0) as Graph<Option<N>, Option<E>, Ty, Ix>) { this.NodeEnd = graph.NodeEnd; this.EdgeEnd = graph.EdgeEnd; this.#g = graph this.#ty = ty; this.#ix = ix; this.#node_count = 0; this.#edge_count = 0; this.#free_node = graph.NodeEnd; this.#free_edge = graph.EdgeEnd; } static directed<N, E, Ix extends GraphIx = 32>(size: Ix = 32 as Ix): StableGraph<N, E, Directed, Ix> { return new StableGraph(Directed, size) } static undirected<N, E, Ix extends GraphIx = 32>(size: Ix = 32 as Ix): StableGraph<N, E, Undirected, Ix> { return new StableGraph(Undirected, size) } static from_edges<N, E, Ty extends EdgeType, Ix extends GraphIx = 32>(ty: Ty, ix: Ix, iterable: Iterable<[number, number, E]>): StableGraph<N, E, Ty, Ix> { const g: StableGraph<N, E, Ty, Ix> = StableGraph.with_capacity(ty, ix, 0, 0); g.extend_with_edges(iterable); return g; } static with_capacity<N, E, Ty extends EdgeType, Ix extends GraphIx = 32>(ty: Ty, ix: Ix, nodes: number, edges: number): StableGraph<N, E, Ty, Ix> { const sg: StableGraph<N, E, Ty, Ix> = new StableGraph(); sg.#g = Graph.with_capacity(ty, ix, nodes, edges); return sg; } clone(): StableGraph<N, E, Ty, Ix> { return new StableGraph(this.#ty, this.#ix, this.#g.clone()) } to_node_index(n: NodeId<this>): number { return n as number; } from_node_index(n: number): NodeId<this> { return n as number; } to_edge_index(e: EdgeId<this>): number { return e as number; } from_edge_index(e: number): EdgeId<this> { return e as number; } visit_map(): VisitMap<NodeId<this>> { return new VisitorFbs(this.node_bound()) } reset_map(map: VisitMap<NodeId<this>> & FixedBitSet): void { map.clear(); map.grow(this.node_bound()) } extend_with_edges(iterable: Iterable<[number, number, E]>) { for (const [source, target, weight] of iterable) { this.#ensure_node_exists(source); this.#ensure_node_exists(target); this.add_edge(source, target, weight) } } raw_nodes(): Node<Option<N>>[] { return this.#g.raw_nodes() } raw_edges(): Edge<Option<E>>[] { return this.#g.raw_edges() } capacity(): [number, number] { return this.#g.capacity(); } reverse() { for (const edge of this.#g.__edges) { swap(edge.node, 0, 1); swap(edge.__next, 0, 1); } for (const node of this.#g.__nodes) { swap(node.__next, 0, 1); } } clear() { this.#node_count = 0; this.#edge_count = 0; this.#free_node = this.NodeEnd; this.#free_edge = this.EdgeEnd; this.#g.clear(); } clear_edges() { this.#edge_count = 0; this.#free_edge = this.EdgeEnd; this.#g.__edges.length = 0; for (const node of this.#g.__nodes) { if (is_some(node.weight)) { node.__next = [this.EdgeEnd, this.EdgeEnd]; } } } node_count(): number { return this.#node_count } edge_count(): number { return this.#edge_count } node_bound(): number { return this.node_count() } edge_bound(): number { return this.edge_count() } node_weight(a: number): Option<N> { return this.#g.__nodes[a].weight; } set_node_weight(a: number, w: N) { return this.#g.__nodes[a].weight = w; } edge_weight(e: number) { return this.#g.__edges[e].weight; } set_edge_weight(e: number, w: E) { return this.#g.__edges[e].weight = w; } is_directed(): boolean { return this.#ty.is_directed(); } contains_node(a: number): boolean { return is_some(this.get_node(a)) } contains_edge(a: number, b: number): boolean { return is_some(this.find_edge(a, b)) } get_node(a: number): Option<Node<Option<N>>> { const node = this.#g.__nodes[a]; return node.weight !== undefined ? node : null } add_node(weight: N): number { if (this.#free_node !== this.NodeEnd) { const node_idx = this.#free_node; this.#occupy_vacant_node(node_idx, weight); return node_idx } else { this.#node_count++; return this.#g.add_node(weight) } } add_edge(a: number, b: number, weight: E): number { let edge_idx: number; let new_edge: Option<Edge<Option<E>>>; let edge: Edge<Option<E>>; if (this.#free_edge !== this.EdgeEnd) { edge_idx = this.#free_edge; edge = this.#g.__edges[edge_idx]; let old = edge.weight; edge.weight = weight; if (old !== undefined) { console.error('old edge weight should be undefined. Edge Index = ' + edge_idx) } this.#free_edge = edge.__next[0]; edge.node = [a, b]; } else { edge_idx = this.#g.__edges.length; new_edge = createEdge( weight, [this.EdgeEnd, this.EdgeEnd], [a, b] ) edge = new_edge; } let wrong_index; const pair = index_twice(this.#g.__nodes, a, b) if (!pair) { wrong_index = Math.max(a, b); } else if (pair.type === 'One') { const an = pair.value as Edge<E>; if (an.weight === undefined) { wrong_index = a; } else { edge.__next = structuredClone(an.__next); an.__next[0] = edge_idx; an.__next[1] = edge_idx; wrong_index = undefined } } else { const [an, bn] = pair.value as [Node<Option<N>>, Node<Option<N>>]; if (an.weight === undefined) { wrong_index = a } else if (bn.weight === undefined) { wrong_index = b; } else { edge.__next = [an.__next[0], bn.__next[1]]; an.__next[0] = edge_idx; bn.__next[1] = edge_idx; wrong_index = undefined; } } if (is_some(wrong_index)) { throw new Error(`StableGraph::add_edge node index ${wrong_index} is not a node in the graph`) } this.#edge_count += 1; if (new_edge) { this.#g.__edges.push(new_edge); } return edge_idx; } update_edge(a: number, b: number, weight: E) { const ix = this.find_edge(a, b); if (is_some(ix)) { this.set_edge_weight(ix, weight); return ix } return this.add_edge(a, b, weight) } remove_node(a: number): Option<N> { const n = this.#g.__nodes[a]; if (!n) { return } const nweight = n.weight; n.weight = undefined if (nweight === undefined) { return } for (const d of DIRECTIONS) { const k = d.index(); while (true) { let next = this.#g.__nodes[a].__next[k]; if (next === this.EdgeEnd) { break; } this.remove_edge(next); } } const nslot = this.#g.__nodes[a]; nslot.__next = [this.#free_node, this.EdgeEnd]; if (this.#free_node !== this.NodeEnd) { this.#g.__nodes[this.#free_node].__next[1] = a; } this.#free_node = a; this.#node_count -= 1; return nweight; } remove_edge(e: number) { const ed = this.#g.__edges[e]; const is_edge = is_some(ed?.weight); const edge_node = ed.node const edge_next = ed.__next if (!is_edge) { return } this.#g.__change_edge_links(edge_node, e, edge_next); const edge = this.#g.__edges[e]; edge.__next = [this.#free_edge, this.EdgeEnd]; edge.node[this.NodeEnd, this.NodeEnd]; this.#free_edge = e; this.#edge_count -= 1; let w = edge.weight; edge.weight = undefined; return w; } find_edge(a: number, b: number): Option<number> { let edge_idx: Option<number>; if (!this.is_directed()) { const ed = this.find_edge_undirected(a, b); edge_idx = ed ? ed[0] : undefined; } else { const node = this.get_node(a); if (node) { const n = this.#g.__find_edge_undirected_from_node(node, b); edge_idx = n ? n[0] : undefined } } return edge_idx } find_edge_undirected(a: number, b: number): Option<[number, Direction]> { const node = this.get_node(a); return node ? this.#g.__find_edge_undirected_from_node(node, b) : undefined } node_weights(): Iterator<N> { return this.#g.node_weights().filter_map(maybe => maybe) } edge_weights(): Iterator<E> { return this.#g.edge_weights().filter_map(maybe => maybe) } node_indices(): Indices { return new Indices(enumerate(this.raw_nodes() as any) as any) } edge_indices(): Indices { return new Indices(enumerate(this.raw_edges() as any) as any) } edge_endpoints(e: number): Option<[number, number]> { const ed = this.#g.__edges[e]; return ed.weight !== undefined ? [ed.source(), ed.target()] : undefined } edges(a: number): Edges<E> { return this.edges_directed(a, Outgoing); } edges_directed(a: number, dir: Direction): Edges<E> { const _next = this.get_node(a); const next: [number, number] = _next ? _next.__next : [this.EdgeEnd, this.EdgeEnd]; return new Edges(a, this.#g.__edges, dir, next, this.#ty) as Edges<E> } edges_connecting(a: number, b: number): EdgesConnecting<E> { return new EdgesConnecting<E>(b, this.edges_directed(a, Direction.Outgoing())) } neighbors(a: number): Neighbors<E> { return this.neighbors_directed(a, Outgoing); } neighbors_directed(a: number, dir: Direction): Neighbors<E> { const it = this.neighbors_undirected(a); if (this.is_directed()) { const k = dir.index(); it.__next[1 - k] = this.EdgeEnd; it.skip_start = this.NodeEnd; } return it; } neighbors_undirected(a: number): Neighbors<E> { const _next = this.get_node(a); const next = is_some(_next) ? structuredClone(_next.__next) : [this.EdgeEnd, this.EdgeEnd] as [number, number]; return new Neighbors(a, this.#g.__edges, next); } node_identifiers(): Iterator<number> { return this.node_indices() } node_references(): Iterator<NodeRef<number, N>> { return new NodeReferences(enumerate(this.__inner.__nodes) as DoubleEndedIterator<[number, Node<N>]>); } edge_references(): Iterator<EdgeRef<number, number, E>> { return new EdgeReferences(enumerate(this.__inner.__edges) as DoubleEndedIterator<[number, Edge<E>]>) } externals(dir: Direction): Externals<E> { return new Externals( this.#ty, iter(this.raw_nodes()).enumerate(), dir, ) as any } map<N2, E2>(node_map: (index: number, weight: N) => N2, edge_map: (index: number, weight: E) => E2): StableGraph<N2, E2, Ty, Ix> { const g = this.#g.map( (i, w) => w === undefined ? undefined : node_map(i, w!), (i, w) => w === undefined ? undefined : edge_map(i, w!) ) const sg: StableGraph<N2, E2, Ty, Ix> = new StableGraph(this.#ty, this.#ix, g); sg.#node_count = this.#node_count; sg.#edge_count = this.#edge_count; sg.#free_node = this.#free_node; sg.#free_edge = this.#free_edge; return sg; } filter_map<N2, E2>(node_map: (index: number, weight: N) => N2, edge_map: (index: number, weight: E) => E2): StableGraph<N2, E2, Ty, Ix> { const nbound = this.node_bound(); const ebound = this.edge_bound(); const result_g = StableGraph.with_capacity(this.#ty, this.#ix, nbound, ebound); let free_node = this.NodeEnd; let free_edge = this.EdgeEnd; for (const [i, node] of enumerate(iter(this.raw_nodes()))) { if (i >= nbound) { break; } if (node.weight !== undefined) { const new_weight = node_map(i, node.weight!); if (new_weight !== undefined) { result_g.add_node(new_weight); continue } } free_node = result_g.#add_vacant_node(free_node) } for (const [i, edge] of enumerate(iter(this.raw_edges()))) { if (i >= ebound) { break } const source = edge.source(); const target = edge.target(); if (edge.weight !== undefined) { if (result_g.contains_node(source) && result_g.contains_node(target)) { const new_weight = edge_map(i, edge.weight!); if (new_weight !== undefined) { result_g.add_edge(source, target, new_weight); continue; } } free_edge = result_g.#add_vacant_edge(free_edge) } } result_g.#free_node = free_node result_g.#free_edge = free_edge result_g.#check_free_lists(); return result_g as StableGraph<N2, E2, Ty, Ix>; } #add_vacant_node(free_node: number) { const nidx = this.#g.add_node(undefined); const nslot = this.#g.__nodes[nidx]; nslot.__next = [free_node, this.EdgeEnd]; if (free_node !== this.NodeEnd) { this.#g.__nodes[free_node].__next[1] = nidx; } return nidx; } #add_vacant_edge(free_edge: number): number { let edge_idx = this.#g.__edges.length; const edge = createEdge( undefined, [this.EdgeEnd, this.EdgeEnd], [this.NodeEnd, this.NodeEnd] ) edge.__next[0] = free_edge; this.#g.__edges.push(edge); return edge_idx; } #occupy_vacant_node(node_idx: number, weight: N) { const nslot = this.#g.__nodes[node_idx]; const old = nslot.weight; nslot.weight = weight; if (old !== undefined) { console.error(`node weight should be undefined at index ${node_idx}`) return } const prevnode = nslot.__next[1] const nextnode = nslot.__next[0] nslot.__next = [this.EdgeEnd, this.EdgeEnd]; if (prevnode !== this.EdgeEnd) { this.#g.__nodes[prevnode].__next[0] = nextnode } if (nextnode !== this.EdgeEnd) { this.#g.__nodes[nextnode].__next[1] = prevnode } if (this.#free_node === node_idx) { this.#free_node = nextnode //! .into_node() ??? } this.#node_count += 1 } #ensure_node_exists(node_ix: number) { if (this.#g.node_weight(node_ix) !== undefined) { return } while (node_ix >= this.#g.node_count()) { let free_node = this.#free_node; free_node = this.#add_vacant_node(free_node) this.#free_node = free_node; } this.#occupy_vacant_node(node_ix, null as N) //! N::default() } #check_free_lists() { const NodeEnd = this.NodeEnd const EdgeEnd = this.EdgeEnd let free_node = this.#free_node; // let prev_free_node = NodeEnd; let free_node_len = 0; while (free_node !== NodeEnd) { const n = this.#g.__nodes[free_node]; if (n.weight === undefined) { // prev_free_node = free_node; free_node = n.__next[0]; free_node_len += 1; continue; } } let free_edge_len = 0; let free_edge = this.#free_edge; while (free_edge !== EdgeEnd) { const e = this.#g.__edges[free_edge]; if (e.weight === undefined) { free_edge = e.__next[0]; free_edge_len += 1; continue } } } } class Edges<E> extends Iterator<EdgeReference<E>> { // starting skip_start: number; #edges: Edge<E>[]; #next: [number, number]; #direction: Direction #ty: EdgeType; constructor(skip_start: number, edges: Edge<E>[], direction: Direction, next: [number, number], ty: EdgeType) { super(); this.skip_start = skip_start; this.#edges = edges this.#next = next; this.#direction = direction; this.#ty = ty; } into_iter(): Iterator<EdgeReference<E>> { return this; } next(): IteratorResult<EdgeReference<E>> { // type direction | iterate over reverse // | // Directed Outgoing | outgoing no // Directed Incoming | incoming no // Undirected Outgoing | both incoming // Undirected Incoming | both outgoing // For iterate_over, "both" is represented as None. // For reverse, "no" is represented as None. const [iterate_over, reverse] = this.#ty.is_directed() ? [this.#direction, null] : [null, this.#direction.opposite()] if (!iterate_over || iterate_over.value === Outgoing.value) { const i = this.#next[0]; const e = this.#edges[i]; if (e && e.weight !== undefined) { const { node: node, weight, __next: next } = e; this.#next[0] = next[0]; return { done: false, value: new EdgeReference( i, reverse?.value === Outgoing.value ? swap_pair(node) : node, weight ) } } } if (!iterate_over || iterate_over.value === Incoming.value) { let edge while (is_some(edge = this.#edges[this.#next[1]])) { const { node: node, weight, __next: next } = edge; if (weight === undefined) throw new Error('expected weight to be defined') const edge_index = this.#next[1]; this.#next[1] = next[1]; if (!iterate_over && node[0] === this.skip_start) { continue; } return { done: false, value: new EdgeReference( edge_index, reverse?.value === Incoming.value ? swap_pair(node) : node, weight ) } } } return done(); } clone(): Edges<E> { return new Edges(this.skip_start, structuredClone(this.#edges), this.#direction, this.#next, this.#ty) } } class EdgesConnecting<E> extends Iterator<EdgeReference<E>> { #target_node: number; #edges: Edges<E>; constructor(target_node: number, edges: Edges<E>) { super() this.#target_node = target_node; this.#edges = edges; } into_iter(): Iterator<EdgeReference<E>> { return this } next(): IteratorResult<EdgeReference<E>> { const target_node = this.#target_node; const n = this.#edges.find(edge => edge.node[1] === target_node); return n ? { done: false, value: n } : done() } } class NodeReferences<N> extends DoubleEndedIterator<NodeRef<number, N>> { #iter: DoubleEndedIterator<[number, Node<N>]>; constructor(it: DoubleEndedIterator<[number, Node<N>]>) { super() this.#iter = it; } into_iter(): DoubleEndedIterator<NodeRef<number, N>> { this.#iter.into_iter(); return this } next(): IteratorResult<NodeRef<number, N>> { const n = this.#iter.find_map(([i, node]) => node.weight !== undefined ? NodeRef(i, node.weight as N) : undefined) return n ? { done: false, value: n } : done() } next_back(): IteratorResult<NodeRef<number, N>> { const n = this.#iter.rfind_map(([i, node]) => node.weight !== undefined ? NodeRef(i, node.weight as N) : undefined) return n ? { done: false, value: n } : done() } } class EdgeReferences<E> extends DoubleEndedIterator<EdgeRef<number, number, E>> { #iter: DoubleEndedIterator<[number, Edge<Option<E>>]> constructor(it: DoubleEndedIterator<[number, Edge<Option<E>>]>) { super(); this.#iter = it; } into_iter(): DoubleEndedIterator<EdgeRef<number, number, E>> { this.#iter.into_iter() return this; } next(): IteratorResult<EdgeRef<number, number, E>> { const n = this.#iter.find_map(([i, e]) => e.weight === undefined ? undefined : EdgeRef(i, e.source(), e.target(), e.weight as E)); return n ? { done: false, value: n } : done(); } next_back(): IteratorResult<EdgeRef<number, number, E>, any> { const n = this.#iter.rfind_map(([i, e]) => e.weight === undefined ? undefined : EdgeRef(i, e.source(), e.target(), e.weight as E)); return n ? { done: false, value: n } : done(); } } class Externals<E> extends Iterator<any> { #iter: Iterator<[number, E]>; #dir: Direction #ty: EdgeType; constructor(ty: EdgeType, it: Iterator<[number, E]>, dir: Direction) { super() this.#iter = it; this.#dir = dir; this.#ty = ty; console.log(this.#dir, this.#ty); } into_iter(): Iterator<any> { this.#iter.into_iter(); return this; } next(): IteratorResult<any> { return done() } } class Neighbors<E> extends Iterator<number> { skip_start: number; __next: [number, number]; #edges: Edge<Option<E>>[] constructor(skip_start: number, edges: Edge<Option<E>>[], next: [number, number]) { super() this.skip_start = skip_start; this.__next = next; this.#edges = edges; } into_iter(): Iterator<number> { return this; } next(): IteratorResult<number, any> { // first, any outgoing edges const e = this.#edges[this.__next[0]]; if (e) { assert(e.weight !== undefined); this.__next[0] = e.__next[0]; return { done: false, value: e.node[1] } } // next, incoming edges // for an undirected graph (traversing both incoming/outoing lists), // make sure to not double count selfloops by skipping them let edge; while (is_some(edge = this.#edges[this.__next[1]])) { assert(edge.weight !== undefined); this.__next[1] = edge.__next[1]; if (edge.node[0] !== this.skip_start) { return { done: false, value: edge.node[0] } } } return done() } } class Indices extends DoubleEndedIterator<number> { #iter: DoubleEndedIterator<[number, Node<any>]> | DoubleEndedIterator<[number, Edge<any>]>; constructor(it: DoubleEndedIterator<[number, Node<any>]> | DoubleEndedIterator<[number, Edge<any>]>) { super() this.#iter = it; } into_iter(): DoubleEndedIterator<number> { this.#iter.into_iter(); return this; } next(): IteratorResult<number, any> { const n = this.#iter.find_map(([i, node]) => node.weight !== undefined ? i : undefined) return n !== undefined ? { done: false, value: n! } : done() } next_back(): IteratorResult<number, any> { const n = this.#iter.rfind_map(([i, node]) => node.weight !== undefined ? i : undefined) return n !== undefined ? { done: false, value: n! } : done() } size_hint(): [number, Option<number>] { const [_, hi] = this.#iter.size_hint() return [0, hi]; } }