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 = GraphMap export function DiGraphMap(): GraphMap { return GraphMap.directed() } export type UnGraphMap = GraphMap export function UnGraphMap(): GraphMap { return GraphMap.undirected() } export type NodeTrait = T & Ord function hash(a: N, b: N) { return `${a}//${b}` } class Cache { #cache: Map; 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(cache: Cache, 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 implements GraphBase { readonly NodeId!: N; readonly EdgeId!: E; readonly NodeWeight!: N; readonly EdgeWeight!: E; #cache: Cache // caches key tuple lookups; #nodes: IndexMap; #edges: IndexMap<([N, N]) & Ord, E>; #ty: Ty; constructor(nodes: IndexMap = 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(): GraphMap { return new GraphMap(new IndexMap(), new IndexMap() as any, Directed) } static undirected(): GraphMap { return new GraphMap(new IndexMap(), new IndexMap() as any, Undirected) } static with_capacity(ty: Ty, nodes: number, edges: number): GraphMap { return new GraphMap( IndexMap.with_capacity(nodes), IndexMap.with_capacity(edges), ty, ) } static from_edges(ty: Ty, iterable: Iterable<[N, N, E]>): GraphMap { const it = iter(iterable) const g = ty.is_directed() ? GraphMap.directed() : GraphMap.undirected(); for (const [src, target, weight] of it) { g.add_edge(src, target, weight); } return g as GraphMap; } static from_graph(graph: Graph): GraphMap { const new_graph = GraphMap.with_capacity(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 { 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): 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, b: NodeId): 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 { return new VisitorSet(); } reset_map(map: VisitMap & VisitorSet) { 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 { return this.#nodes.iter().map(v => v[0]) } node_references(): DoubleEndedIterator> { return new NodeReferences(this.#nodes.iter()) } edge_references(): DoubleEndedIterator> { 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 { 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 { 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 { return new Nodes(this.#nodes.keys()); } neighbors(a: N): Iterator { const iterable = this.#nodes.get(a) ?? []; return new Neighbors(iter(iterable), this.#ty) } neighbors_directed(a: N, dir: Direction): Iterator { 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 { 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` into its equivalent Graph */ into_graph(): Graph { // TODO: add ix to graphmap to pass here instead of hardcoded const gr = Graph.with_capacity(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 extends Iterator { #iter: Iterator<[N, Direction]>; #ty: Ty; constructor(iter: Iterator<[N, Direction]>, ty: Ty) { super() this.#iter = iter; this.#ty = ty; } into_iter(): Iterator { this.#iter.into_iter() return this; } next(): IteratorResult { 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] { const [lower, upper] = this.#iter.size_hint(); return this.#ty.is_directed() ? [0, upper] : [lower, upper] } } class NeighborsDirected extends Iterator { #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 { this.#iter.into_iter() return this } next(): IteratorResult { 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] { const [lower, upper] = this.#iter.size_hint() return this.#ty.is_directed() ? [0, upper] : [lower, upper] } } class NodeReferences extends DoubleEndedIterator> { #iter: DoubleEndedIterator<[N, [N, Direction][]]>; constructor(it: DoubleEndedIterator<[N, [N, Direction][]]>) { super() this.#iter = it; } into_iter(): DoubleEndedIterator> { this.#iter.into_iter(); return this } next(): IteratorResult> { const n = this.#iter.next(); if (!n.done) { const [w] = n.value; return { done: false, value: NodeRef(w, w) } } return done() } next_back(): IteratorResult> { const n = this.#iter.next_back(); if (!n.done) { const [w] = n.value; return { done: false, value: NodeRef(w, w) } } return done() } } class EdgeReferences extends DoubleEndedIterator> { #iter: DoubleEndedIterator<[[N, N], E]>; constructor(it: DoubleEndedIterator<[[N, N], E]>) { super() this.#iter = it; } into_iter(): DoubleEndedIterator> { this.#iter.into_iter(); return this } next(): IteratorResult> { 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> { 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 extends ExactSizeDoubleEndedIterator { #iter: ExactSizeDoubleEndedIterator constructor(it: ExactSizeDoubleEndedIterator) { super() this.#iter = it; } into_iter(): ExactSizeDoubleEndedIterator { return this } next() { return this.#iter.next(); } next_back() { return this.#iter.next_back() } } class Edges extends Iterator<[N, N, E]> { #from: N; #edges: IndexMap<([N, N]) & Ord, E>; #iter: Iterator; #ty: Ty; #cache: Cache; constructor(cache: Cache, from: N, edges: IndexMap<([N, N]) & Ord, E>, iter: Iterator, 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] { return this.#iter.size_hint(); } } class EdgesDirected extends Iterator<[N, N, E]> { #from: N; #dir: Direction; #edges: IndexMap<([N, N]) & Ord, E>; #iter: Iterator #ty: Ty; #cache: Cache constructor(cache: Cache, from: N, dir: Direction, edges: IndexMap<([N, N]) & Ord, E>, iter: Iterator, 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] { return this.#iter.size_hint(); } } class AllEdges 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; } }