UNPKG

joshkaposh-graph

Version:

A collection of general purpose graph data structures and traversal algorithms

690 lines (577 loc) 20.3 kB
import { assert } from "joshkaposh-iterator/src/util"; import { type EdgeType, Directed, Direction, Outgoing, Undirected } from "./graph/shared"; import { DoubleEndedIterator, ExactSizeDoubleEndedIterator, Iterator, done, iter, range } from "joshkaposh-iterator"; import { type Option, is_some } from 'joshkaposh-option' import { Graph } from "./graph/graph"; import { IndexMap } from "joshkaposh-index-map"; import { type VisitMap, type GraphBase, type NodeId, EdgeRef, NodeRef, VisitorSet } from './visit' import type { Ord } from "./util"; import { swap_remove } from "./array-helpers"; export type DiGraphMap<N extends NodeTrait, E> = GraphMap<N, E, Directed> export function DiGraphMap<N extends NodeTrait, E>(): GraphMap<N, E, Directed> { return GraphMap.directed() } export type UnGraphMap<N extends NodeTrait, E> = GraphMap<N, E, Undirected> export function UnGraphMap<N extends NodeTrait, E>(): GraphMap<N, E, Undirected> { return GraphMap.undirected() } export type NodeTrait<T = {}> = T & Ord function hash<N>(a: N, b: N) { return `${a}//${b}` } class Cache<N> { #cache: Map<string, [N, N]>; constructor() { this.#cache = new Map(); } get(a: N, b: N): [N, N] & Ord { const h = hash(a, b) if (!this.#cache.has(h)) { const tup = [a, b] as [N, N] & Ord; this.#cache.set(h, tup); return tup; } return this.#cache.get(h) as [N, N] & Ord } remove(a: N, b: N) { const h = hash(a, b); this.#cache.delete(h); } } function edge_key<N extends NodeTrait>(cache: Cache<N>, a: N, b: N, directed: boolean): [N, N] & Ord { // cache this so lookups will be deterministic return directed || a <= b ? cache.get(a, b) : cache.get(b, a); } export class GraphMap<N extends NodeTrait = any, E = any, Ty extends EdgeType = any> implements GraphBase<N, E, N, E> { readonly NodeId!: N; readonly EdgeId!: E; readonly NodeWeight!: N; readonly EdgeWeight!: E; #cache: Cache<N> // caches key tuple lookups; #nodes: IndexMap<N, [N, Direction][]>; #edges: IndexMap<([N, N]) & Ord, E>; #ty: Ty; constructor(nodes: IndexMap<N, [N, Direction][]> = new IndexMap(), edges: IndexMap<([N, N]) & Ord, E> = new IndexMap(), ty: Ty = Directed as Ty) { this.#nodes = nodes; this.#edges = edges; this.#ty = ty; this.#cache = new Cache(); } static directed<N extends NodeTrait, E>(): GraphMap<N, E, Directed> { return new GraphMap(new IndexMap(), new IndexMap() as any, Directed) } static undirected<N extends NodeTrait, E>(): GraphMap<N, E, Undirected> { return new GraphMap(new IndexMap(), new IndexMap() as any, Undirected) } static with_capacity<N extends NodeTrait, E, Ty extends EdgeType>(ty: Ty, nodes: number, edges: number): GraphMap<N, E, Ty> { return new GraphMap<N, E, Ty>( IndexMap.with_capacity(nodes), IndexMap.with_capacity(edges), ty, ) } static from_edges<N extends NodeTrait, E, Ty extends EdgeType>(ty: Ty, iterable: Iterable<[N, N, E]>): GraphMap<N, E, Ty> { const it = iter(iterable) const g = ty.is_directed() ? GraphMap.directed<N, E>() : GraphMap.undirected<N, E>(); for (const [src, target, weight] of it) { g.add_edge(src, target, weight); } return g as GraphMap<N, E, Ty>; } static from_graph<N extends NodeTrait, E, Ty extends EdgeType>(graph: Graph<N, E, Ty>): GraphMap<N, E, Ty> { const new_graph = GraphMap.with_capacity<N, E, Ty>(graph.ty(), graph.node_count(), graph.edge_count()) for (const node of graph.raw_nodes()) { new_graph.add_node(node.weight); } for (const edge of graph.edge_indices()) { const [a, b] = graph.edge_endpoints(edge)!; new_graph.add_edge( graph.node_weight(a)!, graph.node_weight(b)!, graph.edge_weight(structuredClone(edge))! ) } return new_graph; } clone(): GraphMap<N, E, Ty> { return GraphMap.from_edges(this.#ty, this.#edges.iter().map(([[a, b], w]) => [structuredClone(a), structuredClone(b), structuredClone(w)] as [N, N, E])) } to_node_index(n: NodeId<this>): number { return this.#nodes.get_index_of(n)!; } from_node_index(ix: number): N { assert(ix < this.#nodes.len(), `The requested index ${ix} is out of bounds`) const [key] = this.#nodes.get_index_entry(ix)! return key; } to_edge_index(a: NodeId<this>, b: NodeId<this>): number { return this.#edges.get_index_of(edge_key(this.#cache, a, b, this.is_directed()))!; } from_edge_index(ix: number): E { assert(ix < this.#edges.len(), `The requested index ${ix} is out of bounds`) const [_, w] = this.#edges.get_index_entry(ix)! return w; } node_indices() { return range(0, this.node_count()) } edge_indices() { return range(0, this.edge_count()) } visit_map(): VisitMap<N> { return new VisitorSet<N>(); } reset_map(map: VisitMap<N> & VisitorSet<N>) { map.clear(); } capacity(): [number, number] { return [this.#nodes.len(), this.#edges.len()] } contains_node(n: N): boolean { return this.#nodes.contains_key(n) } contains_edge(a: N, b: N): boolean { return this.#edges.contains_key(edge_key(this.#cache, a, b, this.#ty.is_directed())) } is_directed(): boolean { return this.#ty.is_directed(); } node_count(): number { return this.#nodes.len(); } node_bound(): number { return this.node_count() } node_identifiers(): DoubleEndedIterator<N> { return this.#nodes.iter().map(v => v[0]) } node_references(): DoubleEndedIterator<NodeRef<N, N>> { return new NodeReferences(this.#nodes.iter()) } edge_references(): DoubleEndedIterator<EdgeRef<N, E, E>> { return new EdgeReferences(this.#edges.iter()) } edge_count(): number { return this.#edges.len(); } edge_bound(): number { return this.edge_count() } clear(): void { this.#nodes.clear(); this.#edges.clear(); } add_node(n: N): N { this.#nodes.insert(n, []) return n } remove_node(n: N): boolean { const _links = this.#nodes.swap_remove(n); if (!is_some(_links)) { return false } const links = _links; for (const [succ, dir] of links) { const edge = dir.value === Direction.Outgoing().value ? edge_key(this.#cache, n, succ, this.#ty.is_directed()) : edge_key(this.#cache, succ, n, this.#ty.is_directed()) this.#remove_single_edge(succ, n, dir.opposite()); this.#edges.swap_remove(edge); } return true; } add_edge(a: N, b: N, weight: E): Option<E> { const old = this.#edges.insert(edge_key(this.#cache, a, b, this.#ty.is_directed()), weight); if (is_some(old)) { return old; } else { // insert in the adjacency list if it's a new edge // update if (!this.#nodes.contains_key(a)) { this.#nodes.insert(a, []) } if (!this.#nodes.contains_key(b)) { this.#nodes.insert(b, []) } this.#nodes.get(a)!.push([b, Direction.Outgoing()]) if (a !== b) { // self loops don't have the Incoming entry this.#nodes.get(b)!.push([a, Direction.Incoming()]) } return null; } } update_edge(a: this['NodeId'], b: this['NodeId'], weight: this['EdgeWeight']): this['EdgeId'] { this.add_edge(a, b, weight); return weight } remove_edge(a: N, b: N): Option<E> { const exist1 = this.#remove_single_edge(a, b, Direction.Outgoing()); // @ts-expect-error const exist2 = a !== b ? this.#remove_single_edge(b, a, Direction.Incoming()) : exist1; const edkey = edge_key(this.#cache, a, b, this.#ty.is_directed()); const weight = this.#edges.shift_remove(edkey); return weight; } nodes(): ExactSizeDoubleEndedIterator<N> { return new Nodes(this.#nodes.keys()); } neighbors(a: N): Iterator<N> { const iterable = this.#nodes.get(a) ?? []; return new Neighbors(iter(iterable), this.#ty) } neighbors_directed(a: N, dir: Direction): Iterator<N> { const iterable = this.#nodes.get(a) ?? []; return new NeighborsDirected( iter(iterable), a, dir, this.#ty, ) } edges(a: N): Iterator<[N, N, E]> { return new Edges( this.#cache, a, this.#edges, this.neighbors(a), this.#ty ) } edges_directed(a: N, dir: Direction): Iterator<[N, N, E]> { return new EdgesDirected( this.#cache, a, dir, this.#edges, this.neighbors_directed(a, dir), this.#ty ) } edge_weight(a: N, b: N): Option<E> { return this.#edges.get(edge_key(this.#cache, a, b, this.#ty.is_directed())) } set_edge_weight(a: N, b: N, w: E) { this.#edges.insert(edge_key(this.#cache, a, b, this.#ty.is_directed()), w) } /** * Return an iterator over all edges of the graph with their weight in arbitrary order. */ all_edges(): DoubleEndedIterator<[N, N, E]> { return new AllEdges(iter(this.#edges as any) as any) } /** * Turns a `GraphMap<N, N, E>` into its equivalent Graph */ into_graph(): Graph<N, E, Ty> { // TODO: add ix to graphmap to pass here instead of hardcoded const gr = Graph.with_capacity<N, E, Ty, 32>(this.#ty, 32, this.node_count(), this.edge_count()); for (const [node] of this.#nodes) { gr.add_node(node) } for (const [[a, b], edge_weight] of this.#edges) { const ai = this.#nodes.get_index_of(a)!; const bi = this.#nodes.get_index_of(b)!; gr.add_edge(ai, bi, edge_weight) } return gr; } extend(iterable: Iterable<[N, N, E]>) { for (const elt of iterable) { const [source, target, weight] = elt this.add_edge(source, target, weight); } } // Remove edge relation from a to b // // Returns `true` if it did exist. #remove_single_edge(a: N, b: N, dir: Direction) { const node = this.#nodes.get(a); if (!is_some(node)) { return false; } if (this.#ty.is_directed()) { const index = node.findIndex(elt => elt[0] === b && elt[1].value === dir.value) if (index >= 0) { swap_remove(node, index); return true; } return false; } else { const index = node.findIndex(elt => elt[0] === b) if (is_some(index)) { swap_remove(node, index); return true } return false; } } } class Neighbors<N, Ty extends EdgeType> extends Iterator<N> { #iter: Iterator<[N, Direction]>; #ty: Ty; constructor(iter: Iterator<[N, Direction]>, ty: Ty) { super() this.#iter = iter; this.#ty = ty; } into_iter(): Iterator<N> { this.#iter.into_iter() return this; } next(): IteratorResult<N> { if (this.#ty.is_directed()) { return this.#iter .filter_map(([n, dir]: [N, Direction]) => dir.value === Outgoing.value ? n : undefined) .next(); } else { const n = this.#iter.next(); return n.done ? done() : { done: false, value: n.value[0] }; } } size_hint(): [number, Option<number>] { const [lower, upper] = this.#iter.size_hint(); return this.#ty.is_directed() ? [0, upper] : [lower, upper] } } class NeighborsDirected<N extends NodeTrait, Ty extends EdgeType> extends Iterator<N> { #iter: Iterator<[N, Direction]>; #dir: Direction; #start_node: N; #ty: Ty; constructor(iter: Iterator<[N, Direction]>, start_node: N, dir: Direction, ty: Ty) { super() this.#iter = iter; this.#start_node = start_node; this.#dir = dir; this.#ty = ty; } into_iter(): Iterator<N> { this.#iter.into_iter() return this } next(): IteratorResult<N> { if (this.#ty.is_directed()) { const self_dir = this.#dir; const start_node = this.#start_node; // @ts-expect-error return this.#iter.filter_map(([n, dir]: [N, Direction]) => { if (dir.value === self_dir.value || n === start_node) { return n } }).next(); } else { const n = this.#iter.next(); return n.done ? done() : { done: false, value: n.value[0] }; } } size_hint(): [number, Option<number>] { const [lower, upper] = this.#iter.size_hint() return this.#ty.is_directed() ? [0, upper] : [lower, upper] } } class NodeReferences<N = any> extends DoubleEndedIterator<NodeRef<N, N>> { #iter: DoubleEndedIterator<[N, [N, Direction][]]>; constructor(it: DoubleEndedIterator<[N, [N, Direction][]]>) { super() this.#iter = it; } into_iter(): DoubleEndedIterator<NodeRef<N, N>> { this.#iter.into_iter(); return this } next(): IteratorResult<NodeRef<N, N>> { const n = this.#iter.next(); if (!n.done) { const [w] = n.value; return { done: false, value: NodeRef(w, w) } } return done() } next_back(): IteratorResult<NodeRef<N, N>> { const n = this.#iter.next_back(); if (!n.done) { const [w] = n.value; return { done: false, value: NodeRef(w, w) } } return done() } } class EdgeReferences<N, E> extends DoubleEndedIterator<EdgeRef<N, E, E>> { #iter: DoubleEndedIterator<[[N, N], E]>; constructor(it: DoubleEndedIterator<[[N, N], E]>) { super() this.#iter = it; } into_iter(): DoubleEndedIterator<EdgeRef<N, E, E>> { this.#iter.into_iter(); return this } next(): IteratorResult<EdgeRef<N, E, E>> { const n = this.#iter.next(); if (!n.done) { const [[source, target], weight] = n.value return { done: false, value: EdgeRef(weight, source, target, weight) }; } return done() } next_back(): IteratorResult<EdgeRef<N, E, E>> { const n = this.#iter.next_back(); if (!n.done) { const [[source, target], weight] = n.value return { done: false, value: EdgeRef(weight, source, target, weight) }; } return done() } } class Nodes<N> extends ExactSizeDoubleEndedIterator<N> { #iter: ExactSizeDoubleEndedIterator<N> constructor(it: ExactSizeDoubleEndedIterator<N>) { super() this.#iter = it; } into_iter(): ExactSizeDoubleEndedIterator<N> { return this } next() { return this.#iter.next(); } next_back() { return this.#iter.next_back() } } class Edges<N extends NodeTrait, E, Ty extends EdgeType> extends Iterator<[N, N, E]> { #from: N; #edges: IndexMap<([N, N]) & Ord, E>; #iter: Iterator<N>; #ty: Ty; #cache: Cache<N>; constructor(cache: Cache<N>, from: N, edges: IndexMap<([N, N]) & Ord, E>, iter: Iterator<N>, ty: Ty) { super() this.#from = from; this.#edges = edges; this.#iter = iter; this.#ty = ty; this.#cache = cache } into_iter(): Iterator<[N, N, E]> { this.#iter.into_iter(); return this } next(): IteratorResult<[N, N, E]> { const n = this.#iter.next(); if (!n.done) { const a = this.#from; const b = n.value const edge = this.#edges.get(edge_key(this.#cache, a, b, this.#ty.is_directed())); assert(is_some(edge)); return { done: false, value: [a, b, edge!] } } return done() } size_hint(): [number, Option<number>] { return this.#iter.size_hint(); } } class EdgesDirected<N extends NodeTrait, E, Ty extends EdgeType> extends Iterator<[N, N, E]> { #from: N; #dir: Direction; #edges: IndexMap<([N, N]) & Ord, E>; #iter: Iterator<N> #ty: Ty; #cache: Cache<N> constructor(cache: Cache<N>, from: N, dir: Direction, edges: IndexMap<([N, N]) & Ord, E>, iter: Iterator<N>, ty: Ty) { super() this.#cache = cache this.#from = from; this.#dir = dir; this.#edges = edges; this.#iter = iter; this.#ty = ty; } into_iter(): Iterator<[N, N, E]> { this.#iter.into_iter(); return this } next(): IteratorResult<[N, N, E]> { const n = this.#iter.next(); if (!n.done) { let a = this.#from; let b = n.value; if (this.#dir.value === Direction.Incoming().value) { const temp = a a = b; b = temp; } const edge = this.#edges.get(edge_key(this.#cache, a, b, this.#ty.is_directed()))! assert(is_some(edge)); return { done: false, value: [a, b, edge] } } return done() } size_hint(): [number, Option<number>] { return this.#iter.size_hint(); } } class AllEdges<N extends NodeTrait, E> extends DoubleEndedIterator<[N, N, E]> { #inner: DoubleEndedIterator<[[N, N], E]> constructor(inner: DoubleEndedIterator<[[N, N], E]>) { super(); this.#inner = inner; } into_iter(): DoubleEndedIterator<[N, N, E]> { this.#inner.into_iter(); return this } next(): IteratorResult<[N, N, E]> { const n = this.#inner.next(); if (!n.done) { const [[a, b], weight] = n.value return { done: false, value: [a, b, weight] }; } return done() } next_back(): IteratorResult<[N, N, E]> { const n = this.#inner.next_back(); if (!n.done) { const [[a, b], weight] = n.value return { done: false, value: [a, b, weight] } } return done() } count(): number { return this.#inner.count(); } nth(n: number): IteratorResult<[N, N, E]> { const next = this.#inner.nth(n); if (!next.done) { const [[a, b], weight] = next.value return { done: false, value: [a, b, weight] } } return done(); } last(): Option<[N, N, E]> { const l = this.#inner.last(); if (l) { const [[a, b], weight] = l; return [a, b, weight]; } return null; } }