UNPKG

tree-multimap-typed

Version:
526 lines (525 loc) 17.6 kB
"use strict"; /** * data-structure-typed * * @author Pablo Zeng * @copyright Copyright (c) 2022 Pablo Zeng <zrwusa@gmail.com> * @license MIT License */ Object.defineProperty(exports, "__esModule", { value: true }); exports.DirectedGraph = exports.DirectedEdge = exports.DirectedVertex = void 0; const abstract_graph_1 = require("./abstract-graph"); const utils_1 = require("../../utils"); class DirectedVertex extends abstract_graph_1.AbstractVertex { constructor(key, value) { super(key, value); } } exports.DirectedVertex = DirectedVertex; class DirectedEdge extends abstract_graph_1.AbstractEdge { constructor(src, dest, weight, value) { super(weight, value); this.src = src; this.dest = dest; } } exports.DirectedEdge = DirectedEdge; /** * Directed graph implementation. * @template V - Vertex value type. * @template E - Edge value type. * @template VO - Concrete vertex class (extends AbstractVertex<V>). * @template EO - Concrete edge class (extends AbstractEdge<E>). * @remarks Time O(1), Space O(1) * @example examples will be generated by unit test */ class DirectedGraph extends abstract_graph_1.AbstractGraph { /** * Construct a directed graph with runtime defaults. * @param options - `GraphOptions<V>` (e.g. `vertexValueInitializer`, `defaultEdgeWeight`). * @remarks Time O(1), Space O(1) */ constructor(options) { super(options); this._outEdgeMap = new Map(); this._inEdgeMap = new Map(); } get outEdgeMap() { return this._outEdgeMap; } set outEdgeMap(v) { this._outEdgeMap = v; } get inEdgeMap() { return this._inEdgeMap; } set inEdgeMap(v) { this._inEdgeMap = v; } /** * Construct a directed graph from keys with value initializer `v => v`. * @template K - Vertex key type. * @param keys - Iterable of vertex keys. * @returns DirectedGraph with all keys added. * @remarks Time O(V), Space O(V) */ static fromKeys(keys) { const g = new DirectedGraph({ vertexValueInitializer: (k) => k }); for (const k of keys) g.addVertex(k); return g; } /** * Construct a directed graph from `[key, value]` entries. * @template V - Vertex value type. * @param entries - Iterable of `[key, value]` pairs. * @returns DirectedGraph with all vertices added. * @remarks Time O(V), Space O(V) */ static fromEntries(entries) { const g = new DirectedGraph(); for (const [k, v] of entries) g.addVertex(k, v); return g; } /** * Create a directed vertex instance. Does not insert into the graph. * @param key - Vertex identifier. * @param value - Optional payload. * @returns Concrete vertex instance. * @remarks Time O(1), Space O(1) */ createVertex(key, value) { return new DirectedVertex(key, value); } /** * Create a directed edge instance. Does not insert into the graph. * @param src - Source vertex key. * @param dest - Destination vertex key. * @param weight - Edge weight; defaults to `defaultEdgeWeight`. * @param value - Edge payload. * @returns Concrete edge instance. * @remarks Time O(1), Space O(1) */ createEdge(src, dest, weight, value) { var _a; return new DirectedEdge(src, dest, (_a = weight !== null && weight !== void 0 ? weight : this.options.defaultEdgeWeight) !== null && _a !== void 0 ? _a : 1, value); } /** * Get the unique edge from `src` to `dest`, if present. * @param srcOrKey - Source vertex or key. * @param destOrKey - Destination vertex or key. * @returns Edge instance or `undefined`. * @remarks Time O(1) avg, Space O(1) */ getEdge(srcOrKey, destOrKey) { let edgeMap = []; if (srcOrKey !== undefined && destOrKey !== undefined) { const src = this._getVertex(srcOrKey); const dest = this._getVertex(destOrKey); if (src && dest) { const srcOutEdges = this._outEdgeMap.get(src); if (srcOutEdges) { edgeMap = srcOutEdges.filter(edge => edge.dest === dest.key); } } } return edgeMap[0] || undefined; } /** * Delete edge `src -> dest` if present. * @param srcOrKey - Source vertex or key. * @param destOrKey - Destination vertex or key. * @returns Removed edge or `undefined`. * @remarks Time O(1) avg, Space O(1) */ deleteEdgeSrcToDest(srcOrKey, destOrKey) { const src = this._getVertex(srcOrKey); const dest = this._getVertex(destOrKey); let removed = undefined; if (!src || !dest) { return undefined; } const srcOutEdges = this._outEdgeMap.get(src); if (srcOutEdges) { (0, utils_1.arrayRemove)(srcOutEdges, (edge) => edge.dest === dest.key); } const destInEdges = this._inEdgeMap.get(dest); if (destInEdges) { removed = (0, utils_1.arrayRemove)(destInEdges, (edge) => edge.src === src.key)[0] || undefined; } return removed; } /** * Delete an edge by instance or by `(srcKey, destKey)`. * @param edgeOrSrcVertexKey - Edge instance or source vertex/key. * @param destVertexKey - Optional destination vertex/key when deleting by pair. * @returns Removed edge or `undefined`. * @remarks Time O(1) avg, Space O(1) */ deleteEdge(edgeOrSrcVertexKey, destVertexKey) { let removed = undefined; let src, dest; if (this.isVertexKey(edgeOrSrcVertexKey)) { if (this.isVertexKey(destVertexKey)) { src = this._getVertex(edgeOrSrcVertexKey); dest = this._getVertex(destVertexKey); } else { return; } } else { src = this._getVertex(edgeOrSrcVertexKey.src); dest = this._getVertex(edgeOrSrcVertexKey.dest); } if (src && dest) { const srcOutEdges = this._outEdgeMap.get(src); if (srcOutEdges && srcOutEdges.length > 0) { (0, utils_1.arrayRemove)(srcOutEdges, (edge) => edge.src === src.key && edge.dest === (dest === null || dest === void 0 ? void 0 : dest.key)); } const destInEdges = this._inEdgeMap.get(dest); if (destInEdges && destInEdges.length > 0) { removed = (0, utils_1.arrayRemove)(destInEdges, (edge) => edge.src === src.key && edge.dest === dest.key)[0]; } } return removed; } deleteVertex(vertexOrKey) { let vertexKey; let vertex; if (this.isVertexKey(vertexOrKey)) { vertex = this.getVertex(vertexOrKey); vertexKey = vertexOrKey; } else { vertex = vertexOrKey; vertexKey = this._getVertexKey(vertexOrKey); } if (vertex) { /** * One-step neighbors following outgoing edges. * @param vertexOrKey - Vertex or key. * @returns Array of neighbor vertices. * @remarks Time O(deg_out), Space O(deg_out) */ const neighbors = this.getNeighbors(vertex); for (const neighbor of neighbors) { this.deleteEdgeSrcToDest(vertex, neighbor); } this._outEdgeMap.delete(vertex); this._inEdgeMap.delete(vertex); } return this._vertexMap.delete(vertexKey); } deleteEdgesBetween(v1, v2) { const removed = []; if (v1 && v2) { const v1ToV2 = this.deleteEdgeSrcToDest(v1, v2); const v2ToV1 = this.deleteEdgeSrcToDest(v2, v1); if (v1ToV2) removed.push(v1ToV2); if (v2ToV1) removed.push(v2ToV1); } return removed; } /** * Incoming edges of a vertex. * @param vertexOrKey - Vertex or key. * @returns Array of incoming edges. * @remarks Time O(deg_in), Space O(deg_in) */ incomingEdgesOf(vertexOrKey) { const target = this._getVertex(vertexOrKey); if (target) { return this.inEdgeMap.get(target) || []; } return []; } /** * Outgoing edges of a vertex. * @param vertexOrKey - Vertex or key. * @returns Array of outgoing edges. * @remarks Time O(deg_out), Space O(deg_out) */ outgoingEdgesOf(vertexOrKey) { const target = this._getVertex(vertexOrKey); if (target) { return this._outEdgeMap.get(target) || []; } return []; } /** * Degree (in + out) of a vertex. * @param vertexOrKey - Vertex or key. * @returns Non-negative integer. * @remarks Time O(1) avg, Space O(1) */ degreeOf(vertexOrKey) { /** * In-degree of a vertex. * @param vertexOrKey - Vertex or key. * @returns Non-negative integer. * @remarks Time O(1) avg, Space O(1) */ /** * Out-degree of a vertex. * @param vertexOrKey - Vertex or key. * @returns Non-negative integer. * @remarks Time O(1) avg, Space O(1) */ return this.outDegreeOf(vertexOrKey) + this.inDegreeOf(vertexOrKey); } inDegreeOf(vertexOrKey) { return this.incomingEdgesOf(vertexOrKey).length; } outDegreeOf(vertexOrKey) { return this.outgoingEdgesOf(vertexOrKey).length; } /** * All incident edges of a vertex. * @param vertexOrKey - Vertex or key. * @returns Array of incident edges. * @remarks Time O(deg_in + deg_out), Space O(deg_in + deg_out) */ edgesOf(vertexOrKey) { return [...this.outgoingEdgesOf(vertexOrKey), ...this.incomingEdgesOf(vertexOrKey)]; } getEdgeSrc(e) { return this._getVertex(e.src); } getEdgeDest(e) { return this._getVertex(e.dest); } /** * Direct children reachable by one outgoing edge. * @param vertex - Vertex or key. * @returns Array of neighbor vertices. * @remarks Time O(deg_out), Space O(deg_out) */ getDestinations(vertex) { if (vertex === undefined) { return []; } const destinations = []; const outgoingEdges = this.outgoingEdgesOf(vertex); for (const outEdge of outgoingEdges) { const child = this.getEdgeDest(outEdge); if (child) { destinations.push(child); } } return destinations; } /** * Topological sort if DAG; returns `undefined` if a cycle exists. * @param propertyName - `'key'` to map to keys; `'vertex'` to keep instances. * @returns Array of keys/vertices, or `undefined` when cycle is found. * @remarks Time O(V + E), Space O(V) */ topologicalSort(propertyName) { propertyName = propertyName !== null && propertyName !== void 0 ? propertyName : 'key'; const statusMap = new Map(); for (const entry of this.vertexMap) { statusMap.set(entry[1], 0); } let sorted = []; let hasCycle = false; const dfs = (cur) => { statusMap.set(cur, 1); const children = this.getDestinations(cur); for (const child of children) { const childStatus = statusMap.get(child); if (childStatus === 0) { dfs(child); } else if (childStatus === 1) { hasCycle = true; } } statusMap.set(cur, 2); sorted.push(cur); }; for (const entry of this.vertexMap) { if (statusMap.get(entry[1]) === 0) { dfs(entry[1]); } } if (hasCycle) return undefined; if (propertyName === 'key') sorted = sorted.map(vertex => (vertex instanceof DirectedVertex ? vertex.key : vertex)); return sorted.reverse(); } edgeSet() { let edgeMap = []; this._outEdgeMap.forEach(outEdges => { edgeMap = [...edgeMap, ...outEdges]; }); return edgeMap; } getNeighbors(vertexOrKey) { const neighbors = []; const vertex = this._getVertex(vertexOrKey); if (vertex) { const outEdges = this.outgoingEdgesOf(vertex); for (const outEdge of outEdges) { const neighbor = this._getVertex(outEdge.dest); if (neighbor) { neighbors.push(neighbor); } } } return neighbors; } /** * Resolve an edge's `[src, dest]` endpoints to vertex instances. * @param edge - Edge instance. * @returns `[src, dest]` or `undefined` if either endpoint is missing. * @remarks Time O(1), Space O(1) */ getEndsOfEdge(edge) { if (!this.hasEdge(edge.src, edge.dest)) { return undefined; } const v1 = this._getVertex(edge.src); const v2 = this._getVertex(edge.dest); if (v1 && v2) { return [v1, v2]; } else { return undefined; } } /** * Whether the graph has no vertices and no edges. * @remarks Time O(1), Space O(1) */ isEmpty() { return this.vertexMap.size === 0 && this.inEdgeMap.size === 0 && this.outEdgeMap.size === 0; } /** * Remove all vertices and edges. * @remarks Time O(V + E), Space O(1) */ clear() { this._vertexMap = new Map(); this._inEdgeMap = new Map(); this._outEdgeMap = new Map(); } /** * Deep clone as the same concrete class. * @returns A new graph of the same concrete class (`this` type). * @remarks Time O(V + E), Space O(V + E) */ clone() { return super.clone(); } /** * Tarjan's algorithm for strongly connected components. * @returns `{ dfnMap, lowMap, SCCs }`. * @remarks Time O(V + E), Space O(V + E) */ tarjan() { const dfnMap = new Map(); const lowMap = new Map(); const SCCs = new Map(); let time = 0; const stack = []; const inStack = new Set(); const dfs = (vertex) => { dfnMap.set(vertex, time); lowMap.set(vertex, time); time++; stack.push(vertex); inStack.add(vertex); const neighbors = this.getNeighbors(vertex); for (const neighbor of neighbors) { if (!dfnMap.has(neighbor)) { dfs(neighbor); lowMap.set(vertex, Math.min(lowMap.get(vertex), lowMap.get(neighbor))); } else if (inStack.has(neighbor)) { lowMap.set(vertex, Math.min(lowMap.get(vertex), dfnMap.get(neighbor))); } } if (dfnMap.get(vertex) === lowMap.get(vertex)) { const SCC = []; let poppedVertex; do { poppedVertex = stack.pop(); inStack.delete(poppedVertex); SCC.push(poppedVertex); } while (poppedVertex !== vertex); SCCs.set(SCCs.size, SCC); } }; for (const vertex of this.vertexMap.values()) { if (!dfnMap.has(vertex)) { dfs(vertex); } } return { dfnMap, lowMap, SCCs }; } /** * DFN index map computed by `tarjan()`. * @returns Map from vertex to DFN index. * @remarks Time O(V), Space O(V) */ getDFNMap() { return this.tarjan().dfnMap; } /** * LOW link map computed by `tarjan()`. * @returns Map from vertex to LOW value. * @remarks Time O(V), Space O(V) */ getLowMap() { return this.tarjan().lowMap; } /** * Strongly connected components computed by `tarjan()`. * @returns Map from SCC id to vertices. * @remarks Time O(#SCC + V), Space O(V) */ getSCCs() { return this.tarjan().SCCs; } /** * Internal hook to attach a directed edge into adjacency maps. * @param edge - Edge instance. * @returns `true` if inserted; otherwise `false`. * @remarks Time O(1) avg, Space O(1) */ _addEdge(edge) { if (!(this.hasVertex(edge.src) && this.hasVertex(edge.dest))) { return false; } const srcVertex = this._getVertex(edge.src); const destVertex = this._getVertex(edge.dest); if (srcVertex && destVertex) { const srcOutEdges = this._outEdgeMap.get(srcVertex); if (srcOutEdges) { srcOutEdges.push(edge); } else { this._outEdgeMap.set(srcVertex, [edge]); } const destInEdges = this._inEdgeMap.get(destVertex); if (destInEdges) { destInEdges.push(edge); } else { this._inEdgeMap.set(destVertex, [edge]); } return true; } else { return false; } } } exports.DirectedGraph = DirectedGraph;