trie-typed
Version:
Trie, prefix tree
897 lines (896 loc) • 34.1 kB
JavaScript
"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;