data-structure-typed 2.4.3 → 2.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/.github/workflows/release.yml +27 -0
  2. package/CHANGELOG.md +24 -1
  3. package/README.md +70 -51
  4. package/dist/cjs/index.cjs +486 -167
  5. package/dist/cjs-legacy/index.cjs +487 -165
  6. package/dist/esm/index.mjs +486 -168
  7. package/dist/esm-legacy/index.mjs +487 -166
  8. package/dist/types/common/error.d.ts +23 -0
  9. package/dist/types/common/index.d.ts +1 -0
  10. package/dist/types/data-structures/base/iterable-element-base.d.ts +1 -1
  11. package/dist/types/data-structures/binary-tree/binary-tree.d.ts +15 -5
  12. package/dist/types/data-structures/binary-tree/bst.d.ts +1 -1
  13. package/dist/types/data-structures/binary-tree/red-black-tree.d.ts +7 -1
  14. package/dist/types/data-structures/graph/abstract-graph.d.ts +44 -0
  15. package/dist/types/data-structures/graph/directed-graph.d.ts +3 -2
  16. package/dist/types/data-structures/graph/undirected-graph.d.ts +16 -2
  17. package/dist/types/data-structures/hash/hash-map.d.ts +2 -2
  18. package/dist/types/data-structures/heap/heap.d.ts +3 -7
  19. package/dist/types/data-structures/queue/deque.d.ts +41 -1
  20. package/dist/types/types/data-structures/binary-tree/avl-tree.d.ts +1 -1
  21. package/dist/types/types/data-structures/binary-tree/red-black-tree.d.ts +1 -1
  22. package/dist/types/types/data-structures/linked-list/doubly-linked-list.d.ts +1 -1
  23. package/dist/types/types/data-structures/linked-list/singly-linked-list.d.ts +1 -1
  24. package/dist/types/types/data-structures/priority-queue/priority-queue.d.ts +1 -1
  25. package/dist/types/types/data-structures/queue/deque.d.ts +6 -0
  26. package/dist/types/types/data-structures/stack/stack.d.ts +1 -1
  27. package/dist/umd/data-structure-typed.js +486 -164
  28. package/dist/umd/data-structure-typed.min.js +6 -4
  29. package/package.json +2 -2
  30. package/src/common/error.ts +60 -0
  31. package/src/common/index.ts +2 -0
  32. package/src/data-structures/base/iterable-element-base.ts +5 -4
  33. package/src/data-structures/binary-tree/binary-indexed-tree.ts +6 -5
  34. package/src/data-structures/binary-tree/binary-tree.ts +121 -49
  35. package/src/data-structures/binary-tree/bst.ts +12 -4
  36. package/src/data-structures/binary-tree/red-black-tree.ts +20 -0
  37. package/src/data-structures/binary-tree/tree-map.ts +8 -7
  38. package/src/data-structures/binary-tree/tree-multi-map.ts +4 -4
  39. package/src/data-structures/binary-tree/tree-multi-set.ts +10 -9
  40. package/src/data-structures/binary-tree/tree-set.ts +7 -6
  41. package/src/data-structures/graph/abstract-graph.ts +124 -19
  42. package/src/data-structures/graph/directed-graph.ts +8 -4
  43. package/src/data-structures/graph/map-graph.ts +1 -1
  44. package/src/data-structures/graph/undirected-graph.ts +99 -4
  45. package/src/data-structures/hash/hash-map.ts +19 -6
  46. package/src/data-structures/heap/heap.ts +21 -17
  47. package/src/data-structures/heap/max-heap.ts +2 -3
  48. package/src/data-structures/linked-list/doubly-linked-list.ts +4 -4
  49. package/src/data-structures/linked-list/singly-linked-list.ts +15 -9
  50. package/src/data-structures/matrix/matrix.ts +9 -10
  51. package/src/data-structures/priority-queue/max-priority-queue.ts +2 -3
  52. package/src/data-structures/queue/deque.ts +72 -4
  53. package/src/data-structures/stack/stack.ts +1 -1
  54. package/src/data-structures/trie/trie.ts +12 -6
  55. package/src/types/data-structures/binary-tree/avl-tree.ts +1 -1
  56. package/src/types/data-structures/binary-tree/red-black-tree.ts +1 -1
  57. package/src/types/data-structures/linked-list/doubly-linked-list.ts +1 -1
  58. package/src/types/data-structures/linked-list/singly-linked-list.ts +1 -1
  59. package/src/types/data-structures/priority-queue/priority-queue.ts +1 -1
  60. package/src/types/data-structures/queue/deque.ts +7 -0
  61. package/src/types/data-structures/stack/stack.ts +1 -1
  62. package/src/utils/utils.ts +4 -2
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  import type { Comparator, TreeMultiSetOptions } from '../../types';
12
+ import { ERR } from '../../common';
12
13
  import { RedBlackTree } from './red-black-tree';
13
14
  import { TreeSet } from './tree-set';
14
15
 
@@ -50,18 +51,18 @@ export class TreeMultiSet<K = any, R = K> implements Iterable<K> {
50
51
  if (!this.#isDefaultComparator) return;
51
52
 
52
53
  if (typeof key === 'number') {
53
- if (Number.isNaN(key)) throw new TypeError('TreeMultiSet: NaN is not a valid key');
54
+ if (Number.isNaN(key)) throw new TypeError(ERR.invalidNaN('TreeMultiSet'));
54
55
  return;
55
56
  }
56
57
 
57
58
  if (typeof key === 'string') return;
58
59
 
59
60
  if (key instanceof Date) {
60
- if (Number.isNaN(key.getTime())) throw new TypeError('TreeMultiSet: invalid Date key');
61
+ if (Number.isNaN(key.getTime())) throw new TypeError(ERR.invalidDate('TreeMultiSet'));
61
62
  return;
62
63
  }
63
64
 
64
- throw new TypeError('TreeMultiSet: comparator is required for non-number/non-string/non-Date keys');
65
+ throw new TypeError(ERR.comparatorRequired('TreeMultiSet'));
65
66
  }
66
67
 
67
68
  /**
@@ -69,7 +70,7 @@ export class TreeMultiSet<K = any, R = K> implements Iterable<K> {
69
70
  * @remarks Time O(1), Space O(1)
70
71
  */
71
72
  private _validateCount(n: number): void {
72
- if (!Number.isSafeInteger(n) || n < 0) throw new RangeError('TreeMultiSet: count must be a safe integer >= 0');
73
+ if (!Number.isSafeInteger(n) || n < 0) throw new RangeError(ERR.invalidArgument('count must be a safe integer >= 0.', 'TreeMultiSet'));
73
74
  }
74
75
 
75
76
  /**
@@ -247,7 +248,7 @@ export class TreeMultiSet<K = any, R = K> implements Iterable<K> {
247
248
  * @remarks Time O(1), Space O(1)
248
249
  */
249
250
  get comparator(): Comparator<K> {
250
- return (this.#core as any)._comparator;
251
+ return this.#core.comparator;
251
252
  }
252
253
 
253
254
  // ━━━ clear ━━━
@@ -398,7 +399,7 @@ export class TreeMultiSet<K = any, R = K> implements Iterable<K> {
398
399
  filter(predicate: (key: K, count: number) => boolean): TreeMultiSet<K> {
399
400
  const result = new TreeMultiSet<K>([], {
400
401
  comparator: this.#isDefaultComparator ? undefined : this.comparator,
401
- isMapMode: (this.#core as any)._isMapMode
402
+ isMapMode: this.#core.isMapMode
402
403
  });
403
404
  for (const [k, c] of this.entries()) {
404
405
  if (predicate(k, c)) {
@@ -443,7 +444,7 @@ export class TreeMultiSet<K = any, R = K> implements Iterable<K> {
443
444
  ): TreeMultiSet<K2> {
444
445
  const result = new TreeMultiSet<K2>([], {
445
446
  comparator: options?.comparator,
446
- isMapMode: (this.#core as any)._isMapMode
447
+ isMapMode: this.#core.isMapMode
447
448
  });
448
449
  for (const [k, c] of this.entries()) {
449
450
  const [newKey, newCount] = mapper(k, c);
@@ -464,7 +465,7 @@ export class TreeMultiSet<K = any, R = K> implements Iterable<K> {
464
465
  clone(): TreeMultiSet<K> {
465
466
  const result = new TreeMultiSet<K>([], {
466
467
  comparator: this.#isDefaultComparator ? undefined : this.comparator,
467
- isMapMode: (this.#core as any)._isMapMode
468
+ isMapMode: this.#core.isMapMode
468
469
  });
469
470
  for (const [k, c] of this.entries()) {
470
471
  result.add(k, c);
@@ -486,7 +487,7 @@ export class TreeMultiSet<K = any, R = K> implements Iterable<K> {
486
487
  callback?: C
487
488
  ): (C extends undefined ? K : ReturnType<C>)[] {
488
489
  const cb = callback ?? ((k: K) => k);
489
- return this.#core.rangeSearch(range, node => cb(node.key)) as any;
490
+ return this.#core.rangeSearch(range, node => cb(node.key));
490
491
  }
491
492
 
492
493
  /**
@@ -9,6 +9,7 @@
9
9
 
10
10
  import type { Comparator } from '../../types';
11
11
  import type { TreeSetElementCallback, TreeSetOptions, TreeSetRangeOptions, TreeSetReduceCallback } from '../../types';
12
+ import { ERR } from '../../common';
12
13
  import { RedBlackTree } from './red-black-tree';
13
14
 
14
15
  /**
@@ -66,7 +67,7 @@ export class TreeSet<K = any, R = K> implements Iterable<K> {
66
67
  return (a: K, b: K): number => {
67
68
  // numbers
68
69
  if (typeof a === 'number' && typeof b === 'number') {
69
- if (Number.isNaN(a) || Number.isNaN(b)) throw new TypeError('TreeSet: NaN is not a valid key');
70
+ if (Number.isNaN(a) || Number.isNaN(b)) throw new TypeError(ERR.invalidNaN('TreeSet'));
70
71
  // treat -0 and 0 as equal
71
72
  const aa = Object.is(a, -0) ? 0 : a;
72
73
  const bb = Object.is(b, -0) ? 0 : b;
@@ -82,11 +83,11 @@ export class TreeSet<K = any, R = K> implements Iterable<K> {
82
83
  if (a instanceof Date && b instanceof Date) {
83
84
  const ta = a.getTime();
84
85
  const tb = b.getTime();
85
- if (Number.isNaN(ta) || Number.isNaN(tb)) throw new TypeError('TreeSet: invalid Date key');
86
+ if (Number.isNaN(ta) || Number.isNaN(tb)) throw new TypeError(ERR.invalidDate('TreeSet'));
86
87
  return ta > tb ? 1 : ta < tb ? -1 : 0;
87
88
  }
88
89
 
89
- throw new TypeError('TreeSet: comparator is required for non-number/non-string/non-Date keys');
90
+ throw new TypeError(ERR.comparatorRequired('TreeSet'));
90
91
  };
91
92
  }
92
93
 
@@ -108,19 +109,19 @@ export class TreeSet<K = any, R = K> implements Iterable<K> {
108
109
  if (!this.#isDefaultComparator) return;
109
110
 
110
111
  if (typeof key === 'number') {
111
- if (Number.isNaN(key)) throw new TypeError('TreeSet: NaN is not a valid key');
112
+ if (Number.isNaN(key)) throw new TypeError(ERR.invalidNaN('TreeSet'));
112
113
  return;
113
114
  }
114
115
 
115
116
  if (typeof key === 'string') return;
116
117
 
117
118
  if (key instanceof Date) {
118
- if (Number.isNaN(key.getTime())) throw new TypeError('TreeSet: invalid Date key');
119
+ if (Number.isNaN(key.getTime())) throw new TypeError(ERR.invalidDate('TreeSet'));
119
120
  return;
120
121
  }
121
122
 
122
123
  // Other key types should have provided a comparator, so reaching here means misuse.
123
- throw new TypeError('TreeSet: comparator is required for non-number/non-string/non-Date keys');
124
+ throw new TypeError(ERR.comparatorRequired('TreeSet'));
124
125
  }
125
126
 
126
127
  /**
@@ -8,6 +8,7 @@
8
8
 
9
9
  import type { DijkstraResult, EntryCallback, GraphOptions, VertexKey } from '../../types';
10
10
  import { uuidV4 } from '../../utils';
11
+ import { ERR } from '../../common';
11
12
  import { IterableEntryBase } from '../base';
12
13
  import { IGraph } from '../../interfaces';
13
14
  import { Heap } from '../heap';
@@ -65,7 +66,7 @@ export abstract class AbstractGraph<
65
66
  */
66
67
  constructor(options?: Partial<Record<string, unknown>>) {
67
68
  super();
68
- const graph = (options as any)?.graph as GraphOptions<V> | undefined;
69
+ const graph = (options as { graph?: GraphOptions<V> })?.graph;
69
70
  this._options = { defaultEdgeWeight: 1, ...(graph ?? {}) };
70
71
  }
71
72
 
@@ -274,7 +275,7 @@ export abstract class AbstractGraph<
274
275
  const newEdge = this.createEdge(srcOrEdge, dest, weight, value);
275
276
  return this._addEdge(newEdge);
276
277
  } else {
277
- throw new Error('dest must be a Vertex or vertex key while srcOrEdge is an Edge');
278
+ throw new TypeError(ERR.invalidArgument('dest must be a Vertex or vertex key when srcOrEdge is an Edge.', 'Graph'));
278
279
  }
279
280
  }
280
281
  }
@@ -984,11 +985,12 @@ export abstract class AbstractGraph<
984
985
  * @remarks Time O(1), Space O(1)
985
986
  */
986
987
  protected _createInstance(_options?: Partial<Record<string, unknown>>): this {
987
- const Ctor: any = (this as any).constructor;
988
- const instance: this = new Ctor();
989
- const graph = (_options as any)?.graph as GraphOptions<V> | undefined;
990
- if (graph) (instance as any)._options = { ...(instance as any)._options, ...graph };
991
- else (instance as any)._options = { ...(instance as any)._options, ...(this as any)._options };
988
+ const Ctor = this.constructor as new () => this;
989
+ const instance = new Ctor();
990
+ const graph = (_options as { graph?: GraphOptions<V> })?.graph;
991
+ // Use bracket notation for protected field access on dynamically created instance
992
+ if (graph) instance['_options'] = { ...instance['_options'], ...graph };
993
+ else instance['_options'] = { ...instance['_options'], ...this._options };
992
994
  return instance;
993
995
  }
994
996
 
@@ -1009,28 +1011,27 @@ export abstract class AbstractGraph<
1009
1011
  // 1) Add vertices
1010
1012
  if (iter) {
1011
1013
  for (const [k, v] of iter) {
1012
- (g as any).addVertex(k as VertexKey, v as V | undefined);
1014
+ g.addVertex(k as VertexKey, v as V | undefined);
1013
1015
  }
1014
1016
  } else {
1015
1017
  for (const [k, v] of this) {
1016
- (g as any).addVertex(k as VertexKey, v as V | undefined);
1018
+ g.addVertex(k as VertexKey, v as V | undefined);
1017
1019
  }
1018
1020
  }
1019
1021
  // 2) Add edges whose endpoints exist in the new graph
1020
1022
  const edges = this.edgeSet();
1021
- for (const e of edges as any[]) {
1022
- const ends = this.getEndsOfEdge(e as any) as unknown as [any, any] | undefined;
1023
+ for (const e of edges) {
1024
+ const ends = this.getEndsOfEdge(e);
1023
1025
  if (!ends) continue;
1024
1026
  const [va, vb] = ends;
1025
- const ka = (va as any).key as VertexKey;
1026
- const kb = (vb as any).key as VertexKey;
1027
- const hasA = (g as any).hasVertex ? (g as any).hasVertex(ka) : false;
1028
- const hasB = (g as any).hasVertex ? (g as any).hasVertex(kb) : false;
1027
+ const ka = va.key;
1028
+ const kb = vb.key;
1029
+ // Defensive check for edge cases where hasVertex may be overridden/undefined
1030
+ const hasA = typeof g.hasVertex === 'function' ? g.hasVertex(ka) : false;
1031
+ const hasB = typeof g.hasVertex === 'function' ? g.hasVertex(kb) : false;
1029
1032
  if (hasA && hasB) {
1030
- const w = (e as any).weight;
1031
- const val = (e as any).value;
1032
- const newEdge = (g as any).createEdge(ka, kb, w, val);
1033
- (g as any)._addEdge(newEdge);
1033
+ const newEdge = g.createEdge(ka, kb, e.weight, e.value);
1034
+ (g as this & { _addEdge(edge: EO): boolean })._addEdge(newEdge);
1034
1035
  }
1035
1036
  }
1036
1037
  return g;
@@ -1078,4 +1079,108 @@ export abstract class AbstractGraph<
1078
1079
  protected _getVertexKey(vertexOrKey: VO | VertexKey): VertexKey {
1079
1080
  return vertexOrKey instanceof AbstractVertex ? vertexOrKey.key : vertexOrKey;
1080
1081
  }
1082
+
1083
+ /**
1084
+ * The edge connector string used in visual output.
1085
+ * Override in subclasses (e.g., '--' for undirected, '->' for directed).
1086
+ */
1087
+ protected get _edgeConnector(): string {
1088
+ return '--';
1089
+ }
1090
+
1091
+ /**
1092
+ * Generate a text-based visual representation of the graph.
1093
+ *
1094
+ * **Adjacency list format:**
1095
+ * ```
1096
+ * Graph (5 vertices, 6 edges):
1097
+ * A -> B (1), C (2)
1098
+ * B -> D (3)
1099
+ * C -> (no outgoing edges)
1100
+ * D -> A (1)
1101
+ * E (isolated)
1102
+ * ```
1103
+ *
1104
+ * @param options - Optional display settings.
1105
+ * @param options.showWeight - Whether to show edge weights (default: true).
1106
+ * @returns The visual string.
1107
+ */
1108
+ override toVisual(options?: { showWeight?: boolean }): string {
1109
+ const showWeight = options?.showWeight ?? true;
1110
+ const vertices = [...this._vertexMap.values()];
1111
+ const vertexCount = vertices.length;
1112
+ const edgeCount = this.edgeSet().length;
1113
+
1114
+ const lines: string[] = [`Graph (${vertexCount} vertices, ${edgeCount} edges):`];
1115
+
1116
+ for (const vertex of vertices) {
1117
+ const neighbors = this.getNeighbors(vertex);
1118
+ if (neighbors.length === 0) {
1119
+ lines.push(` ${vertex.key} (isolated)`);
1120
+ } else {
1121
+ const edgeStrs = neighbors.map(neighbor => {
1122
+ const edge = this.getEdge(vertex, neighbor);
1123
+ if (edge && showWeight && edge.weight !== undefined && edge.weight !== 1) {
1124
+ return `${neighbor.key} (${edge.weight})`;
1125
+ }
1126
+ return `${neighbor.key}`;
1127
+ });
1128
+ lines.push(` ${vertex.key} ${this._edgeConnector} ${edgeStrs.join(', ')}`);
1129
+ }
1130
+ }
1131
+
1132
+ return lines.join('\n');
1133
+ }
1134
+
1135
+ /**
1136
+ * Generate DOT language representation for Graphviz.
1137
+ *
1138
+ * @param options - Optional display settings.
1139
+ * @param options.name - Graph name (default: 'G').
1140
+ * @param options.showWeight - Whether to label edges with weight (default: true).
1141
+ * @returns DOT format string.
1142
+ */
1143
+ toDot(options?: { name?: string; showWeight?: boolean }): string {
1144
+ const name = options?.name ?? 'G';
1145
+ const showWeight = options?.showWeight ?? true;
1146
+ const isDirected = this._edgeConnector === '->';
1147
+ const graphType = isDirected ? 'digraph' : 'graph';
1148
+ const edgeOp = isDirected ? '->' : '--';
1149
+
1150
+ const lines: string[] = [`${graphType} ${name} {`];
1151
+
1152
+ // Add all vertices (ensures isolated vertices appear)
1153
+ for (const vertex of this._vertexMap.values()) {
1154
+ lines.push(` "${vertex.key}";`);
1155
+ }
1156
+
1157
+ // Add edges
1158
+ const visited = new Set<string>();
1159
+ for (const vertex of this._vertexMap.values()) {
1160
+ for (const neighbor of this.getNeighbors(vertex)) {
1161
+ const edgeId = isDirected
1162
+ ? `${vertex.key}->${neighbor.key}`
1163
+ : [vertex.key, neighbor.key].sort().join('--');
1164
+ if (visited.has(edgeId)) continue;
1165
+ visited.add(edgeId);
1166
+
1167
+ const edge = this.getEdge(vertex, neighbor);
1168
+ const label = edge && showWeight && edge.weight !== undefined && edge.weight !== 1
1169
+ ? ` [label="${edge.weight}"]`
1170
+ : '';
1171
+ lines.push(` "${vertex.key}" ${edgeOp} "${neighbor.key}"${label};`);
1172
+ }
1173
+ }
1174
+
1175
+ lines.push('}');
1176
+ return lines.join('\n');
1177
+ }
1178
+
1179
+ /**
1180
+ * Print the graph to console.
1181
+ * @param options - Display settings passed to `toVisual`.
1182
+ */
1183
+ override print(options?: { showWeight?: boolean }): void {
1184
+ console.log(this.toVisual(options));
1185
+ }
1081
1186
  }
@@ -180,6 +180,10 @@ export class DirectedGraph<
180
180
  super(options);
181
181
  }
182
182
 
183
+ protected override get _edgeConnector(): string {
184
+ return '->';
185
+ }
186
+
183
187
  protected _outEdgeMap: Map<VO, EO[]> = new Map<VO, EO[]>();
184
188
 
185
189
  get outEdgeMap(): Map<VO, EO[]> {
@@ -207,8 +211,8 @@ export class DirectedGraph<
207
211
  * @returns DirectedGraph with all keys added.
208
212
  * @remarks Time O(V), Space O(V)
209
213
  */
210
- static fromKeys<K extends VertexKey>(keys: Iterable<K>): DirectedGraph<K, any, DirectedVertex<K>, DirectedEdge<any>> {
211
- const g: DirectedGraph<K, any, DirectedVertex<K>, DirectedEdge<any>> = new DirectedGraph<K, any>({
214
+ static fromKeys<K extends VertexKey>(keys: Iterable<K>): DirectedGraph<K, undefined, DirectedVertex<K>, DirectedEdge<undefined>> {
215
+ const g: DirectedGraph<K, undefined, DirectedVertex<K>, DirectedEdge<undefined>> = new DirectedGraph<K, undefined>({
212
216
  vertexValueInitializer: (k: VertexKey) => k as K
213
217
  });
214
218
  for (const k of keys) g.addVertex(k);
@@ -224,8 +228,8 @@ export class DirectedGraph<
224
228
  */
225
229
  static fromEntries<V>(
226
230
  entries: Iterable<[VertexKey, V]>
227
- ): DirectedGraph<V, any, DirectedVertex<V>, DirectedEdge<any>> {
228
- const g: DirectedGraph<V, any, DirectedVertex<V>, DirectedEdge<any>> = new DirectedGraph<V, any>();
231
+ ): DirectedGraph<V, undefined, DirectedVertex<V>, DirectedEdge<undefined>> {
232
+ const g: DirectedGraph<V, undefined, DirectedVertex<V>, DirectedEdge<undefined>> = new DirectedGraph<V, undefined>();
229
233
  for (const [k, v] of entries) g.addVertex(k, v);
230
234
  return g;
231
235
  }
@@ -111,7 +111,7 @@ export class MapGraph<
111
111
  * @remarks Time O(1), Space O(1)
112
112
  */
113
113
  protected override _snapshotOptions(): Record<string, unknown> {
114
- return { ...(super._snapshotOptions() as any), originCoord: this.originCoord, bottomRight: this.bottomRight };
114
+ return { ...super._snapshotOptions(), originCoord: this.originCoord, bottomRight: this.bottomRight };
115
115
  }
116
116
 
117
117
  /**
@@ -232,8 +232,8 @@ export class UndirectedGraph<
232
232
  */
233
233
  static fromKeys<K extends VertexKey>(
234
234
  keys: Iterable<K>
235
- ): UndirectedGraph<K, any, UndirectedVertex<K>, UndirectedEdge<any>> {
236
- const g: UndirectedGraph<K, any, UndirectedVertex<K>, UndirectedEdge<any>> = new UndirectedGraph<K, any>({
235
+ ): UndirectedGraph<K, undefined, UndirectedVertex<K>, UndirectedEdge<undefined>> {
236
+ const g: UndirectedGraph<K, undefined, UndirectedVertex<K>, UndirectedEdge<undefined>> = new UndirectedGraph<K, undefined>({
237
237
  vertexValueInitializer: (k: VertexKey) => k as K
238
238
  });
239
239
  for (const k of keys) g.addVertex(k);
@@ -249,8 +249,8 @@ export class UndirectedGraph<
249
249
  */
250
250
  static fromEntries<V>(
251
251
  entries: Iterable<[VertexKey, V]>
252
- ): UndirectedGraph<V, any, UndirectedVertex<V>, UndirectedEdge<any>> {
253
- const g: UndirectedGraph<V, any, UndirectedVertex<V>, UndirectedEdge<any>> = new UndirectedGraph<V, any>();
252
+ ): UndirectedGraph<V, undefined, UndirectedVertex<V>, UndirectedEdge<undefined>> {
253
+ const g: UndirectedGraph<V, undefined, UndirectedVertex<V>, UndirectedEdge<undefined>> = new UndirectedGraph<V, undefined>();
254
254
  for (const [k, v] of entries) g.addVertex(k, v);
255
255
  return g;
256
256
  }
@@ -566,6 +566,101 @@ export class UndirectedGraph<
566
566
  };
567
567
  }
568
568
 
569
+ /**
570
+ * Find biconnected components using edge-stack Tarjan variant.
571
+ * A biconnected component is a maximal biconnected subgraph.
572
+ * @returns Array of edge arrays, each representing a biconnected component.
573
+ * @remarks Time O(V + E), Space O(V + E)
574
+ */
575
+ getBiconnectedComponents(): EO[][] {
576
+ const dfn = new Map<VO, number>();
577
+ const low = new Map<VO, number>();
578
+ const edgeStack: EO[] = [];
579
+ const components: EO[][] = [];
580
+ let time = 0;
581
+
582
+ const dfs = (vertex: VO, parent: VO | undefined) => {
583
+ dfn.set(vertex, time);
584
+ low.set(vertex, time);
585
+ time++;
586
+
587
+ const neighbors = this.getNeighbors(vertex);
588
+ let childCount = 0;
589
+
590
+ for (const neighbor of neighbors) {
591
+ const edge = this.getEdge(vertex, neighbor);
592
+ if (!edge) continue;
593
+
594
+ if (!dfn.has(neighbor)) {
595
+ childCount++;
596
+ edgeStack.push(edge);
597
+ dfs(neighbor, vertex);
598
+ low.set(vertex, Math.min(low.get(vertex)!, low.get(neighbor)!));
599
+
600
+ // Articulation point found — pop edges to form a component
601
+ if (
602
+ (parent === undefined && childCount > 1) ||
603
+ (parent !== undefined && low.get(neighbor)! >= dfn.get(vertex)!)
604
+ ) {
605
+ const component: EO[] = [];
606
+ let e: EO | undefined;
607
+ do {
608
+ e = edgeStack.pop();
609
+ if (e) component.push(e);
610
+ } while (e && e !== edge);
611
+ if (component.length > 0) components.push(component);
612
+ }
613
+ } else if (neighbor !== parent && dfn.get(neighbor)! < dfn.get(vertex)!) {
614
+ // Back edge (only push once per undirected edge)
615
+ edgeStack.push(edge);
616
+ low.set(vertex, Math.min(low.get(vertex)!, dfn.get(neighbor)!));
617
+ }
618
+ }
619
+ };
620
+
621
+ for (const vertex of this.vertexMap.values()) {
622
+ if (!dfn.has(vertex)) {
623
+ dfs(vertex, undefined);
624
+ // Remaining edges form a component
625
+ if (edgeStack.length > 0) {
626
+ components.push([...edgeStack]);
627
+ edgeStack.length = 0;
628
+ }
629
+ }
630
+ }
631
+
632
+ return components;
633
+ }
634
+
635
+ /**
636
+ * Detect whether the graph contains a cycle.
637
+ * Uses DFS with parent tracking.
638
+ * @returns `true` if a cycle exists, `false` otherwise.
639
+ * @remarks Time O(V + E), Space O(V)
640
+ */
641
+ hasCycle(): boolean {
642
+ const visited = new Set<VO>();
643
+
644
+ const dfs = (vertex: VO, parent: VO | undefined): boolean => {
645
+ visited.add(vertex);
646
+ for (const neighbor of this.getNeighbors(vertex)) {
647
+ if (!visited.has(neighbor)) {
648
+ if (dfs(neighbor, vertex)) return true;
649
+ } else if (neighbor !== parent) {
650
+ return true; // back edge = cycle
651
+ }
652
+ }
653
+ return false;
654
+ };
655
+
656
+ for (const vertex of this.vertexMap.values()) {
657
+ if (!visited.has(vertex)) {
658
+ if (dfs(vertex, undefined)) return true;
659
+ }
660
+ }
661
+ return false;
662
+ }
663
+
569
664
  /**
570
665
  * Get bridges discovered by `tarjan()`.
571
666
  * @returns Array of edges that are bridges.
@@ -15,6 +15,7 @@ import type {
15
15
  } from '../../types';
16
16
  import { IterableEntryBase } from '../base';
17
17
  import { isWeakKey, rangeCheck } from '../../utils';
18
+ import { ERR } from '../../common';
18
19
 
19
20
  /**
20
21
  * Hash-based map. Supports object keys and custom hashing; offers O(1) average set/get/has.
@@ -197,7 +198,7 @@ export class HashMap<K = any, V = any, R = [K, V]> extends IterableEntryBase<K,
197
198
  return this._objMap;
198
199
  }
199
200
 
200
- protected _toEntryFn?: (rawElement: R) => [K, V];
201
+ protected readonly _toEntryFn?: (rawElement: R) => [K, V];
201
202
 
202
203
  /**
203
204
  * Get the raw→entry converter function if present.
@@ -530,14 +531,15 @@ export class LinkedHashMap<K = any, V = any, R = [K, V]> extends IterableEntryBa
530
531
  return this._tail;
531
532
  }
532
533
 
533
- protected _toEntryFn?: (rawElement: R) => [K, V] = (rawElement: R) => {
534
+ protected readonly _toEntryFn?: (rawElement: R) => [K, V] = (rawElement: R) => {
534
535
  if (this.isEntry(rawElement)) {
535
536
  return rawElement;
536
537
  }
537
- throw new Error(
538
- 'If `entryOrRawElements` does not adhere to [key,value], provide `options.toEntryFn` to transform raw records.'
538
+ throw new TypeError(
539
+ ERR.invalidArgument('If elements do not adhere to [key, value], provide options.toEntryFn to transform raw records.', 'HashMap')
539
540
  );
540
541
  };
542
+
541
543
  get toEntryFn() {
542
544
  return this._toEntryFn;
543
545
  }
@@ -712,8 +714,9 @@ export class LinkedHashMap<K = any, V = any, R = [K, V]> extends IterableEntryBa
712
714
  const cur = node;
713
715
  node = node.next;
714
716
  if (predicate(cur.key as K, cur.value as V | undefined, i++, this)) {
715
- if (isWeakKey(cur.key as unknown as object)) {
716
- this._objMap.delete(cur.key as unknown as object);
717
+ const keyToCheck: unknown = cur.key;
718
+ if (isWeakKey(keyToCheck)) {
719
+ this._objMap.delete(keyToCheck);
717
720
  } else {
718
721
  const hash = this._hashFn(cur.key as K);
719
722
  delete this._noObjMap[hash];
@@ -795,6 +798,16 @@ export class LinkedHashMap<K = any, V = any, R = [K, V]> extends IterableEntryBa
795
798
  }
796
799
 
797
800
  protected _deleteNode(node: HashMapLinkedNode<K, V | undefined>): boolean {
801
+ // Remove from hash table
802
+ const key: unknown = node.key;
803
+ if (isWeakKey(key)) {
804
+ this._objMap.delete(key);
805
+ } else {
806
+ const hash = this._hashFn(key as K);
807
+ delete this._noObjMap[hash];
808
+ }
809
+
810
+ // Remove from linked list
798
811
  const { prev, next } = node;
799
812
  prev.next = next;
800
813
  next.prev = prev;
@@ -8,6 +8,7 @@
8
8
 
9
9
  import type { Comparator, DFSOrderPattern, ElementCallback, HeapOptions } from '../../types';
10
10
  import { IterableElementBase } from '../base';
11
+ import { ERR } from '../../common';
11
12
 
12
13
  /**
13
14
  * Binary heap with pluggable comparator; supports fast insertion and removal of the top element.
@@ -611,7 +612,7 @@ export class Heap<E = any, R = any> extends IterableElementBase<E, R> {
611
612
  thisArg?: unknown
612
613
  ): Heap<EM, RM> {
613
614
  const { comparator, toElementFn, ...rest } = options ?? {};
614
- if (!comparator) throw new TypeError('Heap.map requires options.comparator for EM');
615
+ if (!comparator) throw new TypeError(ERR.comparatorRequired('Heap.map'));
615
616
  const out = this._createLike<EM, RM>([], { ...rest, comparator, toElementFn });
616
617
  let i = 0;
617
618
  for (const x of this) {
@@ -639,20 +640,17 @@ export class Heap<E = any, R = any> extends IterableElementBase<E, R> {
639
640
  return out;
640
641
  }
641
642
 
642
- protected _DEFAULT_COMPARATOR = (a: E, b: E): number => {
643
+ protected readonly _DEFAULT_COMPARATOR: Comparator<E> = (a: E, b: E): number => {
643
644
  if (typeof a === 'object' || typeof b === 'object') {
644
- throw TypeError('When comparing object types, define a custom comparator in options.');
645
+ throw new TypeError(ERR.comparatorRequired('Heap'));
645
646
  }
646
- if ((a as unknown as number) > (b as unknown as number)) return 1;
647
- if ((a as unknown as number) < (b as unknown as number)) return -1;
647
+ if (a > b) return 1;
648
+ if (a < b) return -1;
648
649
  return 0;
649
650
  };
650
651
 
651
- protected _comparator: Comparator<E> = this._DEFAULT_COMPARATOR; /**
652
- * Get the comparator used to order elements.
653
- * @remarks Time O(1), Space O(1)
654
- * @returns Comparator function.
655
- */
652
+ protected readonly _comparator: Comparator<E> = this._DEFAULT_COMPARATOR;
653
+
656
654
  /**
657
655
  * Get the comparator used to order elements.
658
656
  * @remarks Time O(1), Space O(1)
@@ -706,9 +704,11 @@ export class Heap<E = any, R = any> extends IterableElementBase<E, R> {
706
704
  */
707
705
 
708
706
  protected _createInstance(options?: HeapOptions<E, R>): this {
709
- const Ctor: any = this.constructor;
710
- const next: any = new Ctor([], { comparator: this.comparator, toElementFn: this.toElementFn, ...(options ?? {}) });
711
- return next as this;
707
+ const Ctor = this.constructor as new (
708
+ elements?: Iterable<E> | Iterable<R>,
709
+ options?: HeapOptions<E, R>
710
+ ) => this;
711
+ return new Ctor([], { comparator: this.comparator, toElementFn: this.toElementFn, ...(options ?? {}) });
712
712
  }
713
713
 
714
714
  /**
@@ -725,8 +725,11 @@ export class Heap<E = any, R = any> extends IterableElementBase<E, R> {
725
725
  elements: Iterable<EM> | Iterable<RM> = [],
726
726
  options?: HeapOptions<EM, RM>
727
727
  ): Heap<EM, RM> {
728
- const Ctor: any = this.constructor;
729
- return new Ctor(elements, options) as Heap<EM, RM>;
728
+ const Ctor = this.constructor as new (
729
+ elements?: Iterable<EM> | Iterable<RM>,
730
+ options?: HeapOptions<EM, RM>
731
+ ) => Heap<EM, RM>;
732
+ return new Ctor(elements, options);
730
733
  }
731
734
 
732
735
  /**
@@ -781,7 +784,7 @@ export class FibonacciHeap<E> {
781
784
  constructor(comparator?: Comparator<E>) {
782
785
  this.clear();
783
786
  this._comparator = comparator || this._defaultComparator;
784
- if (typeof this.comparator !== 'function') throw new Error('FibonacciHeap: comparator must be a function.');
787
+ if (typeof this.comparator !== 'function') throw new TypeError(ERR.notAFunction('comparator', 'FibonacciHeap'));
785
788
  }
786
789
 
787
790
  protected _root?: FibonacciHeapNode<E>;
@@ -813,7 +816,8 @@ export class FibonacciHeap<E> {
813
816
  return this._min;
814
817
  }
815
818
 
816
- protected _comparator: Comparator<E>;
819
+ protected readonly _comparator: Comparator<E>;
820
+
817
821
  get comparator(): Comparator<E> {
818
822
  return this._comparator;
819
823
  }
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import type { HeapOptions } from '../../types';
8
8
  import { Heap } from './heap';
9
+ import { ERR } from '../../common';
9
10
 
10
11
  /**
11
12
  * @template E
@@ -33,9 +34,7 @@ export class MaxHeap<E = any, R = any> extends Heap<E, R> {
33
34
  super(elements, {
34
35
  comparator: (a: E, b: E): number => {
35
36
  if (typeof a === 'object' || typeof b === 'object') {
36
- throw TypeError(
37
- `When comparing object types, a custom comparator must be defined in the constructor's options parameter.`
38
- );
37
+ throw new TypeError(ERR.comparatorRequired('MaxHeap'));
39
38
  }
40
39
  if (a < b) return 1;
41
40
  if (a > b) return -1;