max-priority-queue-typed 2.4.4 → 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 +56 -32
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs-legacy/index.cjs +55 -31
- package/dist/cjs-legacy/index.cjs.map +1 -1
- package/dist/esm/index.mjs +56 -33
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm-legacy/index.mjs +55 -32
- 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/binary-tree/binary-tree.d.ts +10 -0
- 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 +1 -0
- package/dist/types/data-structures/graph/undirected-graph.d.ts +14 -0
- package/dist/types/data-structures/queue/deque.d.ts +41 -1
- package/dist/types/types/data-structures/queue/deque.d.ts +6 -0
- package/dist/umd/max-priority-queue-typed.js +53 -29
- package/dist/umd/max-priority-queue-typed.js.map +1 -1
- package/dist/umd/max-priority-queue-typed.min.js +1 -1
- package/dist/umd/max-priority-queue-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 +3 -2
- package/src/data-structures/binary-tree/binary-indexed-tree.ts +6 -5
- package/src/data-structures/binary-tree/binary-tree.ts +113 -42
- package/src/data-structures/binary-tree/bst.ts +11 -3
- 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 +5 -4
- package/src/data-structures/binary-tree/tree-set.ts +7 -6
- package/src/data-structures/graph/abstract-graph.ts +106 -1
- package/src/data-structures/graph/directed-graph.ts +4 -0
- package/src/data-structures/graph/undirected-graph.ts +95 -0
- package/src/data-structures/hash/hash-map.ts +13 -2
- package/src/data-structures/heap/heap.ts +4 -3
- package/src/data-structures/heap/max-heap.ts +2 -3
- 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 +71 -3
- package/src/data-structures/trie/trie.ts +2 -1
- package/src/types/data-structures/queue/deque.ts +7 -0
- package/src/utils/utils.ts +4 -2
|
@@ -28,7 +28,7 @@ import { IBinaryTree } from '../../interfaces';
|
|
|
28
28
|
import { isComparable, makeTrampoline, makeTrampolineThunk } from '../../utils';
|
|
29
29
|
import { Queue } from '../queue';
|
|
30
30
|
import { IterableEntryBase } from '../base';
|
|
31
|
-
import { DFSOperation, Range } from '../../common';
|
|
31
|
+
import { DFSOperation, ERR, Range } from '../../common';
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* @template K - The type of the key.
|
|
@@ -371,7 +371,7 @@ export class BinaryTree<K = any, V = any, R = any>
|
|
|
371
371
|
if (isMapMode !== undefined) this._isMapMode = isMapMode;
|
|
372
372
|
if (isDuplicate !== undefined) this._isDuplicate = isDuplicate;
|
|
373
373
|
if (typeof toEntryFn === 'function') this._toEntryFn = toEntryFn;
|
|
374
|
-
else if (toEntryFn) throw TypeError('toEntryFn
|
|
374
|
+
else if (toEntryFn) throw new TypeError(ERR.notAFunction('toEntryFn', 'BinaryTree'));
|
|
375
375
|
}
|
|
376
376
|
|
|
377
377
|
if (keysNodesEntriesOrRaws) this.setMany(keysNodesEntriesOrRaws);
|
|
@@ -670,7 +670,7 @@ export class BinaryTree<K = any, V = any, R = any>
|
|
|
670
670
|
if (!this._root) {
|
|
671
671
|
this._setRoot(newNode);
|
|
672
672
|
if (this._isMapMode && newNode !== null && newNode !== undefined) this._store.set(newNode.key, newNode);
|
|
673
|
-
this._size = 1;
|
|
673
|
+
if (newNode !== null) this._size = 1;
|
|
674
674
|
return true;
|
|
675
675
|
}
|
|
676
676
|
|
|
@@ -708,7 +708,7 @@ export class BinaryTree<K = any, V = any, R = any>
|
|
|
708
708
|
potentialParent.right = newNode;
|
|
709
709
|
}
|
|
710
710
|
if (this._isMapMode && newNode !== null && newNode !== undefined) this._store.set(newNode.key, newNode);
|
|
711
|
-
this._size++;
|
|
711
|
+
if (newNode !== null) this._size++;
|
|
712
712
|
return true;
|
|
713
713
|
}
|
|
714
714
|
|
|
@@ -1605,7 +1605,7 @@ export class BinaryTree<K = any, V = any, R = any>
|
|
|
1605
1605
|
|
|
1606
1606
|
/**
|
|
1607
1607
|
* Finds all leaf nodes in the tree.
|
|
1608
|
-
* @remarks Time O(N), visits every node. Space O(H) for recursive
|
|
1608
|
+
* @remarks Time O(N), visits every node. Space O(H) for recursive or iterative stack.
|
|
1609
1609
|
*
|
|
1610
1610
|
* @template C - The type of the callback function.
|
|
1611
1611
|
* @param [callback=this._DEFAULT_NODE_CALLBACK] - Function to call on each leaf node.
|
|
@@ -1636,17 +1636,18 @@ export class BinaryTree<K = any, V = any, R = any>
|
|
|
1636
1636
|
|
|
1637
1637
|
dfs(startNode);
|
|
1638
1638
|
} else {
|
|
1639
|
-
//
|
|
1640
|
-
const
|
|
1639
|
+
// DFS-based (stack) to match recursive order
|
|
1640
|
+
const stack = [startNode];
|
|
1641
1641
|
|
|
1642
|
-
while (
|
|
1643
|
-
const cur =
|
|
1642
|
+
while (stack.length > 0) {
|
|
1643
|
+
const cur = stack.pop()!;
|
|
1644
1644
|
if (this.isRealNode(cur)) {
|
|
1645
1645
|
if (this.isLeaf(cur)) {
|
|
1646
1646
|
leaves.push(callback(cur));
|
|
1647
1647
|
}
|
|
1648
|
-
|
|
1649
|
-
if (this.isRealNode(cur.right))
|
|
1648
|
+
// Push right first so left is processed first (LIFO)
|
|
1649
|
+
if (this.isRealNode(cur.right)) stack.push(cur.right);
|
|
1650
|
+
if (this.isRealNode(cur.left)) stack.push(cur.left);
|
|
1650
1651
|
}
|
|
1651
1652
|
}
|
|
1652
1653
|
}
|
|
@@ -2258,40 +2259,60 @@ export class BinaryTree<K = any, V = any, R = any>
|
|
|
2258
2259
|
options: BinaryTreePrintOptions
|
|
2259
2260
|
): NodeDisplayLayout {
|
|
2260
2261
|
const { isShowNull, isShowUndefined, isShowRedBlackNIL } = options;
|
|
2261
|
-
const emptyDisplayLayout = <NodeDisplayLayout>[['─'], 1, 0, 0];
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
return _buildNodeDisplay(
|
|
2276
|
-
line,
|
|
2277
|
-
width,
|
|
2278
|
-
this._displayAux(node.left, options),
|
|
2279
|
-
this._displayAux(node.right, options)
|
|
2280
|
-
);
|
|
2281
|
-
} else {
|
|
2282
|
-
// Null or Undefined
|
|
2283
|
-
const line = node === undefined ? 'U' : 'N',
|
|
2284
|
-
width = line.length;
|
|
2262
|
+
const emptyDisplayLayout = <NodeDisplayLayout>[['─'], 1, 0, 0];
|
|
2263
|
+
|
|
2264
|
+
// Iterative post-order: compute display layout bottom-up using an explicit stack.
|
|
2265
|
+
// Stages: 0=process left, 1=process right, 2=merge
|
|
2266
|
+
type Frame = {
|
|
2267
|
+
node: BinaryTreeNode<K, V> | null | undefined;
|
|
2268
|
+
stage: 0 | 1 | 2;
|
|
2269
|
+
leftLayout: NodeDisplayLayout;
|
|
2270
|
+
rightLayout: NodeDisplayLayout;
|
|
2271
|
+
};
|
|
2272
|
+
|
|
2273
|
+
const newFrame = (n: BinaryTreeNode<K, V> | null | undefined): Frame => ({
|
|
2274
|
+
node: n, stage: 0, leftLayout: emptyDisplayLayout, rightLayout: emptyDisplayLayout
|
|
2275
|
+
});
|
|
2285
2276
|
|
|
2286
|
-
|
|
2287
|
-
|
|
2277
|
+
const stack: Frame[] = [newFrame(node)];
|
|
2278
|
+
let result: NodeDisplayLayout = emptyDisplayLayout;
|
|
2279
|
+
|
|
2280
|
+
const setChildResult = (layout: NodeDisplayLayout) => {
|
|
2281
|
+
if (stack.length === 0) { result = layout; return; }
|
|
2282
|
+
const parent = stack[stack.length - 1];
|
|
2283
|
+
if (parent.stage === 1) parent.leftLayout = layout;
|
|
2284
|
+
else parent.rightLayout = layout;
|
|
2285
|
+
};
|
|
2286
|
+
|
|
2287
|
+
while (stack.length > 0) {
|
|
2288
|
+
const frame = stack[stack.length - 1];
|
|
2289
|
+
const cur = frame.node;
|
|
2290
|
+
|
|
2291
|
+
if (frame.stage === 0) {
|
|
2292
|
+
// Leaf / empty node — resolve immediately
|
|
2293
|
+
if (this._isDisplayLeaf(cur, options)) {
|
|
2294
|
+
stack.pop();
|
|
2295
|
+
const layout = this._resolveDisplayLeaf(cur, options, emptyDisplayLayout);
|
|
2296
|
+
setChildResult(layout);
|
|
2297
|
+
continue;
|
|
2298
|
+
}
|
|
2299
|
+
frame.stage = 1;
|
|
2300
|
+
stack.push(newFrame(cur!.left));
|
|
2301
|
+
} else if (frame.stage === 1) {
|
|
2302
|
+
frame.stage = 2;
|
|
2303
|
+
stack.push(newFrame(cur!.right));
|
|
2304
|
+
} else {
|
|
2305
|
+
stack.pop();
|
|
2306
|
+
const line = this.isNIL(cur) ? 'S' : String(cur!.key);
|
|
2307
|
+
const layout = BinaryTree._buildNodeDisplay(line, line.length, frame.leftLayout, frame.rightLayout);
|
|
2308
|
+
setChildResult(layout);
|
|
2309
|
+
}
|
|
2288
2310
|
}
|
|
2289
2311
|
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
function _buildNodeDisplay(line: string, width: number, left: NodeDisplayLayout, right: NodeDisplayLayout) {
|
|
2312
|
+
return result;
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
protected static _buildNodeDisplay(line: string, width: number, left: NodeDisplayLayout, right: NodeDisplayLayout) {
|
|
2295
2316
|
const [leftLines, leftWidth, leftHeight, leftMiddle] = left;
|
|
2296
2317
|
const [rightLines, rightWidth, rightHeight, rightMiddle] = right;
|
|
2297
2318
|
const firstLine =
|
|
@@ -2324,7 +2345,57 @@ export class BinaryTree<K = any, V = any, R = any>
|
|
|
2324
2345
|
Math.max(leftHeight, rightHeight) + 2,
|
|
2325
2346
|
leftWidth + Math.floor(width / 2)
|
|
2326
2347
|
];
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
/**
|
|
2351
|
+
* Check if a node is a display leaf (empty, null, undefined, NIL, or real leaf).
|
|
2352
|
+
*/
|
|
2353
|
+
protected _isDisplayLeaf(
|
|
2354
|
+
node: BinaryTreeNode<K, V> | null | undefined,
|
|
2355
|
+
options: BinaryTreePrintOptions
|
|
2356
|
+
): boolean {
|
|
2357
|
+
const { isShowNull, isShowUndefined, isShowRedBlackNIL } = options;
|
|
2358
|
+
// Empty/hidden nodes are always leaves
|
|
2359
|
+
if (node === null && !isShowNull) return true;
|
|
2360
|
+
if (node === undefined && !isShowUndefined) return true;
|
|
2361
|
+
if (this.isNIL(node) && !isShowRedBlackNIL) return true;
|
|
2362
|
+
// Shown null/undefined are leaves (no children to recurse into)
|
|
2363
|
+
if (node === null || node === undefined) return true;
|
|
2364
|
+
// Real node: check if it has any children that would be displayed
|
|
2365
|
+
const hasDisplayableLeft = this._hasDisplayableChild(node.left, options);
|
|
2366
|
+
const hasDisplayableRight = this._hasDisplayableChild(node.right, options);
|
|
2367
|
+
return !hasDisplayableLeft && !hasDisplayableRight;
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
protected _hasDisplayableChild(
|
|
2371
|
+
child: BinaryTreeNode<K, V> | null | undefined,
|
|
2372
|
+
options: BinaryTreePrintOptions
|
|
2373
|
+
): boolean {
|
|
2374
|
+
if (child === null) return !!options.isShowNull;
|
|
2375
|
+
if (child === undefined) return !!options.isShowUndefined;
|
|
2376
|
+
if (this.isNIL(child)) return !!options.isShowRedBlackNIL;
|
|
2377
|
+
return true; // real node is always displayable
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
/**
|
|
2381
|
+
* Resolve a display leaf node to its layout.
|
|
2382
|
+
*/
|
|
2383
|
+
protected _resolveDisplayLeaf(
|
|
2384
|
+
node: BinaryTreeNode<K, V> | null | undefined,
|
|
2385
|
+
options: BinaryTreePrintOptions,
|
|
2386
|
+
emptyDisplayLayout: NodeDisplayLayout
|
|
2387
|
+
): NodeDisplayLayout {
|
|
2388
|
+
const { isShowNull, isShowUndefined, isShowRedBlackNIL } = options;
|
|
2389
|
+
if (node === null && !isShowNull) return emptyDisplayLayout;
|
|
2390
|
+
if (node === undefined && !isShowUndefined) return emptyDisplayLayout;
|
|
2391
|
+
if (this.isNIL(node) && !isShowRedBlackNIL) return emptyDisplayLayout;
|
|
2392
|
+
if (node !== null && node !== undefined) {
|
|
2393
|
+
const line = this.isNIL(node) ? 'S' : String(node.key);
|
|
2394
|
+
return BinaryTree._buildNodeDisplay(line, line.length, emptyDisplayLayout, emptyDisplayLayout);
|
|
2327
2395
|
}
|
|
2396
|
+
// Shown null / undefined
|
|
2397
|
+
const line = node === undefined ? 'U' : 'N';
|
|
2398
|
+
return BinaryTree._buildNodeDisplay(line, line.length, [[''], 1, 0, 0], [[''], 1, 0, 0]);
|
|
2328
2399
|
}
|
|
2329
2400
|
|
|
2330
2401
|
/**
|
|
@@ -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.
|
|
@@ -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
|
/**
|
|
@@ -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';
|
|
@@ -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
|
}
|
|
@@ -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
|
}
|