stack-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.
- package/dist/cjs/index.cjs +53 -27
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs-legacy/index.cjs +52 -26
- package/dist/cjs-legacy/index.cjs.map +1 -1
- package/dist/esm/index.mjs +53 -28
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm-legacy/index.mjs +52 -27
- package/dist/esm-legacy/index.mjs.map +1 -1
- package/dist/types/common/error.d.ts +23 -0
- package/dist/types/common/index.d.ts +1 -0
- package/dist/types/data-structures/base/iterable-element-base.d.ts +1 -1
- package/dist/types/data-structures/binary-tree/binary-tree.d.ts +15 -5
- package/dist/types/data-structures/binary-tree/bst.d.ts +1 -1
- package/dist/types/data-structures/binary-tree/red-black-tree.d.ts +7 -1
- package/dist/types/data-structures/graph/abstract-graph.d.ts +44 -0
- package/dist/types/data-structures/graph/directed-graph.d.ts +3 -2
- package/dist/types/data-structures/graph/undirected-graph.d.ts +16 -2
- package/dist/types/data-structures/hash/hash-map.d.ts +2 -2
- package/dist/types/data-structures/heap/heap.d.ts +3 -7
- package/dist/types/data-structures/queue/deque.d.ts +41 -1
- package/dist/types/types/data-structures/binary-tree/avl-tree.d.ts +1 -1
- package/dist/types/types/data-structures/binary-tree/red-black-tree.d.ts +1 -1
- package/dist/types/types/data-structures/linked-list/doubly-linked-list.d.ts +1 -1
- package/dist/types/types/data-structures/linked-list/singly-linked-list.d.ts +1 -1
- package/dist/types/types/data-structures/priority-queue/priority-queue.d.ts +1 -1
- package/dist/types/types/data-structures/queue/deque.d.ts +6 -0
- package/dist/types/types/data-structures/stack/stack.d.ts +1 -1
- package/dist/umd/stack-typed.js +50 -24
- package/dist/umd/stack-typed.js.map +1 -1
- package/dist/umd/stack-typed.min.js +1 -1
- package/dist/umd/stack-typed.min.js.map +1 -1
- package/package.json +2 -2
- package/src/common/error.ts +60 -0
- package/src/common/index.ts +2 -0
- package/src/data-structures/base/iterable-element-base.ts +5 -4
- package/src/data-structures/binary-tree/binary-indexed-tree.ts +6 -5
- package/src/data-structures/binary-tree/binary-tree.ts +121 -49
- package/src/data-structures/binary-tree/bst.ts +12 -4
- package/src/data-structures/binary-tree/red-black-tree.ts +20 -0
- package/src/data-structures/binary-tree/tree-map.ts +8 -7
- package/src/data-structures/binary-tree/tree-multi-map.ts +4 -4
- package/src/data-structures/binary-tree/tree-multi-set.ts +10 -9
- package/src/data-structures/binary-tree/tree-set.ts +7 -6
- package/src/data-structures/graph/abstract-graph.ts +124 -19
- package/src/data-structures/graph/directed-graph.ts +8 -4
- package/src/data-structures/graph/map-graph.ts +1 -1
- package/src/data-structures/graph/undirected-graph.ts +99 -4
- package/src/data-structures/hash/hash-map.ts +19 -6
- package/src/data-structures/heap/heap.ts +21 -17
- package/src/data-structures/heap/max-heap.ts +2 -3
- package/src/data-structures/linked-list/doubly-linked-list.ts +4 -4
- package/src/data-structures/linked-list/singly-linked-list.ts +15 -9
- package/src/data-structures/matrix/matrix.ts +9 -10
- package/src/data-structures/priority-queue/max-priority-queue.ts +2 -3
- package/src/data-structures/queue/deque.ts +72 -4
- package/src/data-structures/stack/stack.ts +1 -1
- package/src/data-structures/trie/trie.ts +12 -6
- package/src/types/data-structures/binary-tree/avl-tree.ts +1 -1
- package/src/types/data-structures/binary-tree/red-black-tree.ts +1 -1
- package/src/types/data-structures/linked-list/doubly-linked-list.ts +1 -1
- package/src/types/data-structures/linked-list/singly-linked-list.ts +1 -1
- package/src/types/data-structures/priority-queue/priority-queue.ts +1 -1
- package/src/types/data-structures/queue/deque.ts +7 -0
- package/src/types/data-structures/stack/stack.ts +1 -1
- package/src/utils/utils.ts +4 -2
|
@@ -26,7 +26,7 @@ import { BinaryTree } from './binary-tree';
|
|
|
26
26
|
import { IBinaryTree } from '../../interfaces';
|
|
27
27
|
import { Queue } from '../queue';
|
|
28
28
|
import { isComparable } from '../../utils';
|
|
29
|
-
import { Range } from '../../common';
|
|
29
|
+
import { ERR, Range } from '../../common';
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Represents a Node in a Binary Search Tree.
|
|
@@ -391,7 +391,7 @@ export class BST<K = any, V = any, R = any> extends BinaryTree<K, V, R> implemen
|
|
|
391
391
|
|
|
392
392
|
* @remarks Time O(1) Space O(1)
|
|
393
393
|
*/
|
|
394
|
-
protected _comparator: Comparator<K>;
|
|
394
|
+
protected readonly _comparator: Comparator<K>;
|
|
395
395
|
|
|
396
396
|
/**
|
|
397
397
|
* Gets the comparator function used by the tree.
|
|
@@ -1523,10 +1523,18 @@ export class BST<K = any, V = any, R = any> extends BinaryTree<K, V, R> implemen
|
|
|
1523
1523
|
return 0;
|
|
1524
1524
|
}
|
|
1525
1525
|
|
|
1526
|
+
// Date keys: compare by getTime()
|
|
1527
|
+
if (a instanceof Date && b instanceof Date) {
|
|
1528
|
+
const ta = a.getTime();
|
|
1529
|
+
const tb = b.getTime();
|
|
1530
|
+
if (Number.isNaN(ta) || Number.isNaN(tb)) throw new TypeError(ERR.invalidDate('BST'));
|
|
1531
|
+
return ta > tb ? 1 : ta < tb ? -1 : 0;
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1526
1534
|
// If keys are objects and no comparator is provided, throw an error
|
|
1527
1535
|
if (typeof a === 'object' || typeof b === 'object') {
|
|
1528
|
-
throw TypeError(
|
|
1529
|
-
|
|
1536
|
+
throw new TypeError(
|
|
1537
|
+
ERR.comparatorRequired('BST')
|
|
1530
1538
|
);
|
|
1531
1539
|
}
|
|
1532
1540
|
|
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
FamilyPosition, NodePredicate,
|
|
14
14
|
OptNode,
|
|
15
15
|
RBTNColor,
|
|
16
|
+
IterationType,
|
|
16
17
|
RedBlackTreeOptions
|
|
17
18
|
} from '../../types';
|
|
18
19
|
import { BST } from './bst';
|
|
@@ -918,6 +919,25 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
|
|
|
918
919
|
* @param [thisArg] - See parameter type for details.
|
|
919
920
|
* @returns A new RedBlackTree with mapped entries.
|
|
920
921
|
*/
|
|
922
|
+
/**
|
|
923
|
+
* Red-Black trees are self-balancing — `perfectlyBalance` rebuilds via
|
|
924
|
+
* sorted bulk insert, which naturally produces a balanced RBT.
|
|
925
|
+
* @remarks Time O(N), Space O(N)
|
|
926
|
+
*/
|
|
927
|
+
override perfectlyBalance(iterationType?: IterationType): boolean {
|
|
928
|
+
// Extract sorted entries, clear, re-insert — RBT self-balances on insert
|
|
929
|
+
const entries: [K, V | undefined][] = [];
|
|
930
|
+
for (const [key, value] of this) entries.push([key, value]);
|
|
931
|
+
if (entries.length <= 1) return true;
|
|
932
|
+
this.clear();
|
|
933
|
+
this.setMany(
|
|
934
|
+
entries.map(([k]) => k),
|
|
935
|
+
entries.map(([, v]) => v),
|
|
936
|
+
true // isBalanceAdd
|
|
937
|
+
);
|
|
938
|
+
return true;
|
|
939
|
+
}
|
|
940
|
+
|
|
921
941
|
override map<MK = K, MV = V, MR = any>(
|
|
922
942
|
callback: EntryCallback<K, V | undefined, [MK, MV]>,
|
|
923
943
|
options?: Partial<RedBlackTreeOptions<MK, MV, MR>>,
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import type { Comparator } from '../../types';
|
|
11
11
|
import type { TreeMapEntryCallback, TreeMapOptions, TreeMapRangeOptions, TreeMapReduceCallback } from '../../types';
|
|
12
12
|
import { RedBlackTree } from './red-black-tree';
|
|
13
|
+
import { ERR } from '../../common';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* An ordered Map backed by a red-black tree.
|
|
@@ -58,7 +59,7 @@ export class TreeMap<K = any, V = any, R = [K, V]> implements Iterable<[K, V | u
|
|
|
58
59
|
} else {
|
|
59
60
|
// Validate entries like native Map: each item must be a 2-tuple-like value.
|
|
60
61
|
if (!Array.isArray(item) || item.length < 2) {
|
|
61
|
-
throw new TypeError('TreeMap
|
|
62
|
+
throw new TypeError(ERR.invalidEntry('TreeMap'));
|
|
62
63
|
}
|
|
63
64
|
k = item[0] as K;
|
|
64
65
|
v = item[1] as V | undefined;
|
|
@@ -81,7 +82,7 @@ export class TreeMap<K = any, V = any, R = [K, V]> implements Iterable<[K, V | u
|
|
|
81
82
|
static createDefaultComparator<K>(): Comparator<K> {
|
|
82
83
|
return (a: K, b: K): number => {
|
|
83
84
|
if (typeof a === 'number' && typeof b === 'number') {
|
|
84
|
-
if (Number.isNaN(a) || Number.isNaN(b)) throw new TypeError('TreeMap
|
|
85
|
+
if (Number.isNaN(a) || Number.isNaN(b)) throw new TypeError(ERR.invalidNaN('TreeMap'));
|
|
85
86
|
const aa = Object.is(a, -0) ? 0 : a;
|
|
86
87
|
const bb = Object.is(b, -0) ? 0 : b;
|
|
87
88
|
return aa > bb ? 1 : aa < bb ? -1 : 0;
|
|
@@ -94,11 +95,11 @@ export class TreeMap<K = any, V = any, R = [K, V]> implements Iterable<[K, V | u
|
|
|
94
95
|
if (a instanceof Date && b instanceof Date) {
|
|
95
96
|
const ta = a.getTime();
|
|
96
97
|
const tb = b.getTime();
|
|
97
|
-
if (Number.isNaN(ta) || Number.isNaN(tb)) throw new TypeError('TreeMap
|
|
98
|
+
if (Number.isNaN(ta) || Number.isNaN(tb)) throw new TypeError(ERR.invalidDate('TreeMap'));
|
|
98
99
|
return ta > tb ? 1 : ta < tb ? -1 : 0;
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
throw new TypeError('TreeMap
|
|
102
|
+
throw new TypeError(ERR.comparatorRequired('TreeMap'));
|
|
102
103
|
};
|
|
103
104
|
}
|
|
104
105
|
|
|
@@ -106,18 +107,18 @@ export class TreeMap<K = any, V = any, R = [K, V]> implements Iterable<[K, V | u
|
|
|
106
107
|
if (!this.#isDefaultComparator) return;
|
|
107
108
|
|
|
108
109
|
if (typeof key === 'number') {
|
|
109
|
-
if (Number.isNaN(key)) throw new TypeError('TreeMap
|
|
110
|
+
if (Number.isNaN(key)) throw new TypeError(ERR.invalidNaN('TreeMap'));
|
|
110
111
|
return;
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
if (typeof key === 'string') return;
|
|
114
115
|
|
|
115
116
|
if (key instanceof Date) {
|
|
116
|
-
if (Number.isNaN(key.getTime())) throw new TypeError('TreeMap
|
|
117
|
+
if (Number.isNaN(key.getTime())) throw new TypeError(ERR.invalidDate('TreeMap'));
|
|
117
118
|
return;
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
throw new TypeError('TreeMap
|
|
121
|
+
throw new TypeError(ERR.comparatorRequired('TreeMap'));
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
/**
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { Comparator, TreeMultiMapOptions } from '../../types';
|
|
10
|
-
import { Range } from '../../common';
|
|
10
|
+
import { ERR, Range } from '../../common';
|
|
11
11
|
import { RedBlackTree, RedBlackTreeNode } from './red-black-tree';
|
|
12
12
|
import { TreeSet } from './tree-set';
|
|
13
13
|
|
|
@@ -261,15 +261,15 @@ export class TreeMultiMap<K = any, V = any, R = any> implements Iterable<[K, V[]
|
|
|
261
261
|
// reuse TreeSet strict validation (same policy)
|
|
262
262
|
// NOTE: TreeSet._validateKey is private, so we replicate the checks.
|
|
263
263
|
if (typeof key === 'number') {
|
|
264
|
-
if (Number.isNaN(key)) throw new TypeError('TreeMultiMap
|
|
264
|
+
if (Number.isNaN(key)) throw new TypeError(ERR.invalidNaN('TreeMultiMap'));
|
|
265
265
|
return;
|
|
266
266
|
}
|
|
267
267
|
if (typeof key === 'string') return;
|
|
268
268
|
if (key instanceof Date) {
|
|
269
|
-
if (Number.isNaN(key.getTime())) throw new TypeError('TreeMultiMap
|
|
269
|
+
if (Number.isNaN(key.getTime())) throw new TypeError(ERR.invalidDate('TreeMultiMap'));
|
|
270
270
|
return;
|
|
271
271
|
}
|
|
272
|
-
throw new TypeError('TreeMultiMap
|
|
272
|
+
throw new TypeError(ERR.comparatorRequired('TreeMultiMap'));
|
|
273
273
|
}
|
|
274
274
|
|
|
275
275
|
/**
|
|
@@ -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
|
|
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
|
|
61
|
+
if (Number.isNaN(key.getTime())) throw new TypeError(ERR.invalidDate('TreeMultiSet'));
|
|
61
62
|
return;
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
throw new TypeError('TreeMultiSet
|
|
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('
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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))
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
988
|
-
const instance
|
|
989
|
-
const graph = (_options as
|
|
990
|
-
|
|
991
|
-
|
|
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
|
-
|
|
1014
|
+
g.addVertex(k as VertexKey, v as V | undefined);
|
|
1013
1015
|
}
|
|
1014
1016
|
} else {
|
|
1015
1017
|
for (const [k, v] of this) {
|
|
1016
|
-
|
|
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
|
|
1022
|
-
const ends = this.getEndsOfEdge(e
|
|
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 =
|
|
1026
|
-
const kb =
|
|
1027
|
-
|
|
1028
|
-
const
|
|
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
|
|
1031
|
-
|
|
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,
|
|
211
|
-
const g: DirectedGraph<K,
|
|
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,
|
|
228
|
-
const g: DirectedGraph<V,
|
|
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 { ...
|
|
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,
|
|
236
|
-
const g: UndirectedGraph<K,
|
|
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,
|
|
253
|
-
const g: UndirectedGraph<V,
|
|
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.
|