UNPKG

trie-typed

Version:
897 lines (896 loc) • 34.1 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.AbstractGraph = exports.AbstractEdge = exports.AbstractVertex = void 0; const utils_1 = require("../../utils"); const base_1 = require("../base"); const heap_1 = require("../heap"); const queue_1 = require("../queue"); class AbstractVertex { constructor(key, value) { this.key = key; this.value = value; } } exports.AbstractVertex = AbstractVertex; class AbstractEdge { constructor(weight, value) { this.weight = weight !== undefined ? weight : 1; this.value = value; this._hashCode = (0, utils_1.uuidV4)(); } get hashCode() { return this._hashCode; } } exports.AbstractEdge = AbstractEdge; /** * Abstract graph over vertices and edges. * @template V - Vertex value type. * @template E - Edge value type. * @template VO - Concrete vertex subclass (extends AbstractVertex<V>). * @template EO - Concrete edge subclass (extends AbstractEdge<E>). * @remarks Time O(1), Space O(1) * @example examples will be generated by unit test */ class AbstractGraph extends base_1.IterableEntryBase { /** * Construct a graph with runtime defaults. * @param options - `GraphOptions<V>` in `options.graph` (e.g. `vertexValueInitializer`, `defaultEdgeWeight`). * @remarks Time O(1), Space O(1) */ constructor(options) { super(); this._options = { defaultEdgeWeight: 1 }; this._vertexMap = new Map(); const graph = options === null || options === void 0 ? void 0 : options.graph; this._options = Object.assign({ defaultEdgeWeight: 1 }, (graph !== null && graph !== void 0 ? graph : {})); } get options() { return this._options; } get vertexMap() { return this._vertexMap; } set vertexMap(v) { this._vertexMap = v; } get size() { return this._vertexMap.size; } /** * Get vertex instance by key. * @param vertexKey - Vertex key. * @returns Vertex instance or `undefined`. * @remarks Time O(1), Space O(1) */ getVertex(vertexKey) { return this._vertexMap.get(vertexKey) || undefined; } /** * Whether a vertex exists. * @param vertexOrKey - Vertex or key. * @returns `true` if present, otherwise `false`. * @remarks Time O(1) avg, Space O(1) */ hasVertex(vertexOrKey) { return this._vertexMap.has(this._getVertexKey(vertexOrKey)); } /** * Add a vertex by key/value or by pre-built vertex. * @param keyOrVertex - Vertex key or existing vertex instance. * @param value - Optional payload. * @returns `true` if inserted; `false` when key already exists. * @remarks Time O(1) avg, Space O(1) */ addVertex(keyOrVertex, value) { if (keyOrVertex instanceof AbstractVertex) { return this._addVertex(keyOrVertex); } else { const newVertex = this.createVertex(keyOrVertex, value); return this._addVertex(newVertex); } } /** * Type guard: check if a value is a valid vertex key. * @param potentialKey - Value to test. * @returns `true` if string/number; else `false`. * @remarks Time O(1), Space O(1) */ isVertexKey(potentialKey) { const potentialKeyType = typeof potentialKey; return potentialKeyType === 'string' || potentialKeyType === 'number'; } /** * Delete multiple vertices. * @param vertexMap - Array of vertices or keys. * @returns `true` if any vertex was removed. * @remarks Time O(sum(deg)), Space O(1) */ removeManyVertices(vertexMap) { const removed = []; for (const v of vertexMap) { removed.push(this.deleteVertex(v)); } return removed.length > 0; } /** * Whether an edge exists between two vertices. * @param v1 - Endpoint A vertex or key. * @param v2 - Endpoint B vertex or key. * @returns `true` if present; otherwise `false`. * @remarks Time O(1) avg, Space O(1) */ hasEdge(v1, v2) { const edge = this.getEdge(v1, v2); return !!edge; } /** * Add an edge by instance or by `(src, dest, weight?, value?)`. * @param srcOrEdge - Edge instance or source vertex/key. * @param dest - Destination vertex/key (when adding by pair). * @param weight - Edge weight. * @param value - Edge payload. * @returns `true` if inserted; otherwise `false`. * @remarks Time O(1) avg, Space O(1) */ addEdge(srcOrEdge, dest, weight, value) { if (srcOrEdge instanceof AbstractEdge) { return this._addEdge(srcOrEdge); } else { if (dest instanceof AbstractVertex || typeof dest === 'string' || typeof dest === 'number') { if (!(this.hasVertex(srcOrEdge) && this.hasVertex(dest))) return false; if (srcOrEdge instanceof AbstractVertex) srcOrEdge = srcOrEdge.key; if (dest instanceof AbstractVertex) dest = dest.key; const newEdge = this.createEdge(srcOrEdge, dest, weight, value); return this._addEdge(newEdge); } else { throw new Error('dest must be a Vertex or vertex key while srcOrEdge is an Edge'); } } } /** * Set the weight of an existing edge. * @param srcOrKey - Source vertex or key. * @param destOrKey - Destination vertex or key. * @param weight - New weight. * @returns `true` if updated; otherwise `false`. * @remarks Time O(1) avg, Space O(1) */ setEdgeWeight(srcOrKey, destOrKey, weight) { const edge = this.getEdge(srcOrKey, destOrKey); if (edge) { edge.weight = weight; return true; } else { return false; } } /** * Enumerate simple paths up to a limit. * @param v1 - Source vertex or key. * @param v2 - Destination vertex or key. * @param limit - Maximum number of paths to collect. * @returns Array of paths (each path is an array of vertices). * @remarks Time O(paths) worst-case exponential, Space O(V + paths) */ getAllPathsBetween(v1, v2, limit = 1000) { const paths = []; const vertex1 = this._getVertex(v1); const vertex2 = this._getVertex(v2); if (!(vertex1 && vertex2)) { return []; } const stack = []; stack.push({ vertex: vertex1, path: [vertex1] }); while (stack.length > 0) { const { vertex, path } = stack.pop(); if (vertex === vertex2) { paths.push(path); if (paths.length >= limit) return paths; } const neighbors = this.getNeighbors(vertex); for (const neighbor of neighbors) { if (!path.includes(neighbor)) { const newPath = [...path, neighbor]; stack.push({ vertex: neighbor, path: newPath }); } } } return paths; } /** * Sum the weights along a vertex path. * @param path - Sequence of vertices. * @returns Path weight sum (0 if empty or edge missing). * @remarks Time O(L), Space O(1) where L is path length */ getPathSumWeight(path) { var _a; let sum = 0; for (let i = 0; i < path.length; i++) { sum += ((_a = this.getEdge(path[i], path[i + 1])) === null || _a === void 0 ? void 0 : _a.weight) || 0; } return sum; } /** * Minimum hops/weight between two vertices. * @param v1 - Source vertex or key. * @param v2 - Destination vertex or key. * @param isWeight - If `true`, compare by path weight; otherwise by hop count. * @returns Minimum cost or `undefined` if missing/unreachable. * @remarks Time O((V + E) log V) weighted / O(V + E) unweighted, Space O(V + E) */ getMinCostBetween(v1, v2, isWeight) { if (isWeight === undefined) isWeight = false; if (isWeight) { const allPaths = this.getAllPathsBetween(v1, v2); let min = Number.MAX_SAFE_INTEGER; for (const path of allPaths) { min = Math.min(this.getPathSumWeight(path), min); } return min; } else { const vertex2 = this._getVertex(v2); const vertex1 = this._getVertex(v1); if (!(vertex1 && vertex2)) { return undefined; } const visited = new Map(); const queue = new queue_1.Queue([vertex1]); visited.set(vertex1, true); let cost = 0; while (queue.length > 0) { for (let i = 0, layerSize = queue.length; i < layerSize; i++) { const cur = queue.shift(); if (cur === vertex2) { return cost; } if (cur !== undefined) { const neighbors = this.getNeighbors(cur); for (const neighbor of neighbors) { if (!visited.has(neighbor)) { visited.set(neighbor, true); queue.push(neighbor); } } } } cost++; } return undefined; } } /** * Minimum path (as vertex sequence) between two vertices. * @param v1 - Source vertex or key. * @param v2 - Destination vertex or key. * @param isWeight - If `true`, compare by path weight; otherwise by hop count. * @param isDFS - For weighted mode only: if `true`, brute-force all paths; if `false`, use Dijkstra. * @returns Vertex sequence, or `undefined`/empty when unreachable depending on branch. * @remarks Time O((V + E) log V) weighted / O(V + E) unweighted, Space O(V + E) */ getMinPathBetween(v1, v2, isWeight, isDFS = false) { var _a, _b; if (isWeight === undefined) isWeight = false; if (isWeight) { if (isDFS) { const allPaths = this.getAllPathsBetween(v1, v2, 10000); let min = Number.MAX_SAFE_INTEGER; let minIndex = -1; let index = 0; for (const path of allPaths) { const pathSumWeight = this.getPathSumWeight(path); if (pathSumWeight < min) { min = pathSumWeight; minIndex = index; } index++; } return allPaths[minIndex] || undefined; } else { /** * Dijkstra (binary-heap) shortest paths for non-negative weights. * @param src - Source vertex or key. * @param dest - Optional destination for early stop. * @param getMinDist - If `true`, compute global minimum distance. * @param genPaths - If `true`, also generate path arrays. * @returns Result bag or `undefined` if source missing. * @remarks Time O((V + E) log V), Space O(V + E) */ return (_b = (_a = this.dijkstra(v1, v2, true, true)) === null || _a === void 0 ? void 0 : _a.minPath) !== null && _b !== void 0 ? _b : []; } } else { let minPath = []; const vertex1 = this._getVertex(v1); const vertex2 = this._getVertex(v2); if (!(vertex1 && vertex2)) return []; const dfs = (cur, dest, visiting, path) => { visiting.add(cur); if (cur === dest) { minPath = [vertex1, ...path]; return; } const neighbors = this.getNeighbors(cur); for (const neighbor of neighbors) { if (!visiting.has(neighbor)) { path.push(neighbor); dfs(neighbor, dest, visiting, path); path.pop(); } } visiting.delete(cur); }; dfs(vertex1, vertex2, new Set(), []); return minPath; } } /** * Dijkstra without heap (array-based selection). * @param src - Source vertex or key. * @param dest - Optional destination for early stop. * @param getMinDist - If `true`, compute global minimum distance. * @param genPaths - If `true`, also generate path arrays. * @returns Result bag or `undefined` if source missing. * @remarks Time O(V^2 + E), Space O(V + E) */ dijkstraWithoutHeap(src, dest = undefined, getMinDist = false, genPaths = false) { let minDist = Number.MAX_SAFE_INTEGER; let minDest = undefined; let minPath = []; const paths = []; const vertexMap = this._vertexMap; const distMap = new Map(); const seen = new Set(); const preMap = new Map(); const srcVertex = this._getVertex(src); const destVertex = dest ? this._getVertex(dest) : undefined; if (!srcVertex) { return undefined; } for (const vertex of vertexMap) { const vertexOrKey = vertex[1]; if (vertexOrKey instanceof AbstractVertex) distMap.set(vertexOrKey, Number.MAX_SAFE_INTEGER); } distMap.set(srcVertex, 0); preMap.set(srcVertex, undefined); const getMinOfNoSeen = () => { let min = Number.MAX_SAFE_INTEGER; let minV = undefined; for (const [key, value] of distMap) { if (!seen.has(key)) { if (value < min) { min = value; minV = key; } } } return minV; }; const getPaths = (minV) => { for (const vertex of vertexMap) { const vertexOrKey = vertex[1]; if (vertexOrKey instanceof AbstractVertex) { const path = [vertexOrKey]; let parent = preMap.get(vertexOrKey); while (parent) { path.push(parent); parent = preMap.get(parent); } const reversed = path.reverse(); if (vertex[1] === minV) minPath = reversed; paths.push(reversed); } } }; for (let i = 1; i < vertexMap.size; i++) { const cur = getMinOfNoSeen(); if (cur) { seen.add(cur); if (destVertex && destVertex === cur) { if (getMinDist) { minDist = distMap.get(destVertex) || Number.MAX_SAFE_INTEGER; } if (genPaths) { getPaths(destVertex); } return { distMap, preMap, seen, paths, minDist, minPath }; } const neighbors = this.getNeighbors(cur); for (const neighbor of neighbors) { if (!seen.has(neighbor)) { const edge = this.getEdge(cur, neighbor); if (edge) { const curFromMap = distMap.get(cur); const neighborFromMap = distMap.get(neighbor); if (curFromMap !== undefined && neighborFromMap !== undefined) { if (edge.weight + curFromMap < neighborFromMap) { distMap.set(neighbor, edge.weight + curFromMap); preMap.set(neighbor, cur); } } } } } } } if (getMinDist) distMap.forEach((d, v) => { if (v !== srcVertex) { if (d < minDist) { minDist = d; if (genPaths) minDest = v; } } }); if (genPaths) getPaths(minDest); return { distMap, preMap, seen, paths, minDist, minPath }; } dijkstra(src, dest = undefined, getMinDist = false, genPaths = false) { var _a; let minDist = Number.MAX_SAFE_INTEGER; let minDest = undefined; let minPath = []; const paths = []; const vertexMap = this._vertexMap; const distMap = new Map(); const seen = new Set(); const preMap = new Map(); const srcVertex = this._getVertex(src); const destVertex = dest ? this._getVertex(dest) : undefined; if (!srcVertex) return undefined; for (const vertex of vertexMap) { const vertexOrKey = vertex[1]; if (vertexOrKey instanceof AbstractVertex) distMap.set(vertexOrKey, Number.MAX_SAFE_INTEGER); } const heap = new heap_1.Heap([], { comparator: (a, b) => a.key - b.key }); heap.add({ key: 0, value: srcVertex }); distMap.set(srcVertex, 0); preMap.set(srcVertex, undefined); const getPaths = (minV) => { for (const vertex of vertexMap) { const vertexOrKey = vertex[1]; if (vertexOrKey instanceof AbstractVertex) { const path = [vertexOrKey]; let parent = preMap.get(vertexOrKey); while (parent) { path.push(parent); parent = preMap.get(parent); } const reversed = path.reverse(); if (vertex[1] === minV) minPath = reversed; paths.push(reversed); } } }; while (heap.size > 0) { const curHeapNode = heap.poll(); const dist = curHeapNode === null || curHeapNode === void 0 ? void 0 : curHeapNode.key; const cur = curHeapNode === null || curHeapNode === void 0 ? void 0 : curHeapNode.value; if (dist !== undefined) { if (cur) { seen.add(cur); if (destVertex && destVertex === cur) { if (getMinDist) { minDist = distMap.get(destVertex) || Number.MAX_SAFE_INTEGER; } if (genPaths) { getPaths(destVertex); } return { distMap, preMap, seen, paths, minDist, minPath }; } const neighbors = this.getNeighbors(cur); for (const neighbor of neighbors) { if (!seen.has(neighbor)) { const weight = (_a = this.getEdge(cur, neighbor)) === null || _a === void 0 ? void 0 : _a.weight; if (typeof weight === 'number') { const distSrcToNeighbor = distMap.get(neighbor); if (distSrcToNeighbor !== undefined) { if (dist + weight < distSrcToNeighbor) { heap.add({ key: dist + weight, value: neighbor }); preMap.set(neighbor, cur); distMap.set(neighbor, dist + weight); } } } } } } } } if (getMinDist) { distMap.forEach((d, v) => { if (v !== srcVertex) { if (d < minDist) { minDist = d; if (genPaths) minDest = v; } } }); } if (genPaths) { getPaths(minDest); } return { distMap, preMap, seen, paths, minDist, minPath }; } /** * Bellman-Ford single-source shortest paths with option to scan negative cycles. * @param src - Source vertex or key. * @param scanNegativeCycle - If `true`, also detect negative cycles. * @param getMin - If `true`, compute global minimum distance. * @param genPath - If `true`, generate path arrays via predecessor map. * @returns Result bag including distances, predecessors, and optional cycle flag. * @remarks Time O(V * E), Space O(V + E) */ bellmanFord(src, scanNegativeCycle, getMin, genPath) { if (getMin === undefined) getMin = false; if (genPath === undefined) genPath = false; const srcVertex = this._getVertex(src); const paths = []; const distMap = new Map(); const preMap = new Map(); let min = Number.MAX_SAFE_INTEGER; let minPath = []; let hasNegativeCycle; if (scanNegativeCycle) hasNegativeCycle = false; if (!srcVertex) return { hasNegativeCycle, distMap, preMap, paths, min, minPath }; const vertexMap = this._vertexMap; const numOfVertices = vertexMap.size; const edgeMap = this.edgeSet(); const numOfEdges = edgeMap.length; this._vertexMap.forEach(vertex => { distMap.set(vertex, Number.MAX_SAFE_INTEGER); }); distMap.set(srcVertex, 0); for (let i = 1; i < numOfVertices; ++i) { for (let j = 0; j < numOfEdges; ++j) { const ends = this.getEndsOfEdge(edgeMap[j]); if (ends) { const [s, d] = ends; const weight = edgeMap[j].weight; const sWeight = distMap.get(s); const dWeight = distMap.get(d); if (sWeight !== undefined && dWeight !== undefined) { if (distMap.get(s) !== Number.MAX_SAFE_INTEGER && sWeight + weight < dWeight) { distMap.set(d, sWeight + weight); if (genPath) preMap.set(d, s); } } } } } let minDest = undefined; if (getMin) { distMap.forEach((d, v) => { if (v !== srcVertex) { if (d < min) { min = d; if (genPath) minDest = v; } } }); } if (genPath) { for (const vertex of vertexMap) { const vertexOrKey = vertex[1]; if (vertexOrKey instanceof AbstractVertex) { const path = [vertexOrKey]; let parent = preMap.get(vertexOrKey); while (parent !== undefined) { path.push(parent); parent = preMap.get(parent); } const reversed = path.reverse(); if (vertex[1] === minDest) minPath = reversed; paths.push(reversed); } } } for (let j = 0; j < numOfEdges; ++j) { const ends = this.getEndsOfEdge(edgeMap[j]); if (ends) { const [s] = ends; const weight = edgeMap[j].weight; const sWeight = distMap.get(s); if (sWeight) { if (sWeight !== Number.MAX_SAFE_INTEGER && sWeight + weight < sWeight) hasNegativeCycle = true; } } } return { hasNegativeCycle, distMap, preMap, paths, min, minPath }; } /** * Floyd–Warshall all-pairs shortest paths. * @returns `{ costs, predecessor }` matrices. * @remarks Time O(V^3), Space O(V^2) */ floydWarshall() { var _a; const idAndVertices = [...this._vertexMap]; const n = idAndVertices.length; const costs = []; const predecessor = []; for (let i = 0; i < n; i++) { costs[i] = []; predecessor[i] = []; for (let j = 0; j < n; j++) { predecessor[i][j] = undefined; } } for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { costs[i][j] = ((_a = this.getEdge(idAndVertices[i][1], idAndVertices[j][1])) === null || _a === void 0 ? void 0 : _a.weight) || Number.MAX_SAFE_INTEGER; } } for (let k = 0; k < n; k++) { for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { if (costs[i][j] > costs[i][k] + costs[k][j]) { costs[i][j] = costs[i][k] + costs[k][j]; predecessor[i][j] = idAndVertices[k][1]; } } } } return { costs, predecessor }; } /** * Enumerate simple cycles (may be expensive). * @param isInclude2Cycle - If `true`, include 2-cycles when graph semantics allow. * @returns Array of cycles (each as array of vertex keys). * @remarks Time exponential in worst-case, Space O(V + E) */ getCycles(isInclude2Cycle = false) { const cycles = []; const visited = new Set(); const dfs = (vertex, currentPath, visited) => { if (visited.has(vertex)) { if (((!isInclude2Cycle && currentPath.length > 2) || (isInclude2Cycle && currentPath.length >= 2)) && currentPath[0] === vertex.key) { cycles.push([...currentPath]); } return; } visited.add(vertex); currentPath.push(vertex.key); for (const neighbor of this.getNeighbors(vertex)) { if (neighbor) dfs(neighbor, currentPath, visited); } visited.delete(vertex); currentPath.pop(); }; for (const vertex of this.vertexMap.values()) { dfs(vertex, [], visited); } const uniqueCycles = new Map(); for (const cycle of cycles) { const sorted = [...cycle].sort().toString(); if (uniqueCycles.has(sorted)) continue; else { uniqueCycles.set(sorted, cycle); } } /** * Map entries to an array via callback. * @param callback - `(key, value, index, self) => T`. * @param thisArg - Optional `this` for callback. * @returns Mapped results. * @remarks Time O(V), Space O(V) */ return [...uniqueCycles].map(cycleString => cycleString[1]); } /** * Induced-subgraph filter: keep vertices where `predicate(key, value)` is true, * and only keep edges whose endpoints both survive. * @param predicate - `(key, value, index, self) => boolean`. * @param thisArg - Optional `this` for callback. * @returns A new graph of the same concrete class (`this` type). * @remarks Time O(V + E), Space O(V + E) */ filter(predicate, thisArg) { const filtered = []; let index = 0; for (const [key, value] of this) { if (predicate.call(thisArg, key, value, index, this)) { filtered.push([key, value]); } index++; } return this._createLike(filtered, this._snapshotOptions()); } /** * Preserve the old behavior: return filtered entries as an array. * @remarks Time O(V), Space O(V) */ filterEntries(predicate, thisArg) { const filtered = []; let index = 0; for (const [key, value] of this) { if (predicate.call(thisArg, key, value, index, this)) { filtered.push([key, value]); } index++; } return filtered; } map(callback, thisArg) { const mapped = []; let index = 0; for (const [key, value] of this) { mapped.push(callback.call(thisArg, key, value, index, this)); index++; } return mapped; } /** * Create a deep clone of the graph with the same species. * @remarks Time O(V + E), Space O(V + E) */ /** * Create a deep clone of the graph with the same species. * @returns A new graph of the same concrete class (`this` type). * @remarks Time O(V + E), Space O(V + E) */ clone() { return this._createLike(undefined, this._snapshotOptions()); } // ===== Same-species factory & cloning helpers ===== /** * Internal iterator over `[key, value]` entries in insertion order. * @returns Iterator of `[VertexKey, V | undefined]`. * @remarks Time O(V), Space O(1) */ *_getIterator() { for (const vertex of this._vertexMap.values()) { yield [vertex.key, vertex.value]; } } /** * Capture configuration needed to reproduce the current graph. * Currently the graph has no runtime options, so we return an empty object. */ /** * Capture configuration needed to reproduce the current graph. * @returns Options bag (opaque to callers). * @remarks Time O(1), Space O(1) */ _snapshotOptions() { return { graph: Object.assign({}, this._options) }; } /** * Create an empty graph instance of the same concrete species (Directed/Undirected/etc). * @remarks Time O(1), Space O(1) */ /** * Create an empty graph instance of the same concrete species. * @param _options - Snapshot options from `_snapshotOptions()`. * @returns A new empty graph instance of `this` type. * @remarks Time O(1), Space O(1) */ _createInstance(_options) { const Ctor = this.constructor; const instance = new Ctor(); const graph = _options === null || _options === void 0 ? void 0 : _options.graph; if (graph) instance._options = Object.assign(Object.assign({}, instance._options), graph); else instance._options = Object.assign(Object.assign({}, instance._options), this._options); return instance; } /** * Create a same-species graph populated with the given entries. * Also preserves edges between kept vertices from the source graph. * @remarks Time O(V + E), Space O(V + E) */ /** * Create a same-species graph populated with entries; preserves edges among kept vertices. * @param iter - Optional entries to seed the new graph. * @param options - Snapshot options. * @returns A new graph of `this` type. * @remarks Time O(V + E), Space O(V + E) */ _createLike(iter, options) { const g = this._createInstance(options); // 1) Add vertices if (iter) { for (const [k, v] of iter) { g.addVertex(k, v); } } else { for (const [k, v] of this) { g.addVertex(k, v); } } // 2) Add edges whose endpoints exist in the new graph const edges = this.edgeSet(); for (const e of edges) { const ends = this.getEndsOfEdge(e); if (!ends) continue; const [va, vb] = ends; const ka = va.key; const kb = vb.key; const hasA = g.hasVertex ? g.hasVertex(ka) : false; const hasB = g.hasVertex ? g.hasVertex(kb) : false; if (hasA && hasB) { const w = e.weight; const val = e.value; const newEdge = g.createEdge(ka, kb, w, val); g._addEdge(newEdge); } } return g; } /** * Insert a pre-built vertex into the graph. * @param newVertex - Concrete vertex instance. * @returns `true` if inserted; `false` if key already exists. * @remarks Time O(1) avg, Space O(1) */ _addVertex(newVertex) { if (this.hasVertex(newVertex)) { return false; } this._vertexMap.set(newVertex.key, newVertex); return true; } /** * Resolve a vertex key or instance to the concrete vertex instance. * @param vertexOrKey - Vertex key or existing vertex. * @returns Vertex instance or `undefined`. * @remarks Time O(1), Space O(1) */ _getVertex(vertexOrKey) { const vertexKey = this._getVertexKey(vertexOrKey); return this._vertexMap.get(vertexKey) || undefined; } /** * Resolve a vertex key from a key or vertex instance. * @param vertexOrKey - Vertex key or existing vertex. * @returns The vertex key. * @remarks Time O(1), Space O(1) */ _getVertexKey(vertexOrKey) { return vertexOrKey instanceof AbstractVertex ? vertexOrKey.key : vertexOrKey; } } exports.AbstractGraph = AbstractGraph;