avl-tree-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.
Files changed (45) hide show
  1. package/dist/cjs/index.cjs +154 -67
  2. package/dist/cjs/index.cjs.map +1 -1
  3. package/dist/cjs-legacy/index.cjs +152 -65
  4. package/dist/cjs-legacy/index.cjs.map +1 -1
  5. package/dist/esm/index.mjs +154 -67
  6. package/dist/esm/index.mjs.map +1 -1
  7. package/dist/esm-legacy/index.mjs +152 -65
  8. package/dist/esm-legacy/index.mjs.map +1 -1
  9. package/dist/types/common/error.d.ts +23 -0
  10. package/dist/types/common/index.d.ts +1 -0
  11. package/dist/types/data-structures/binary-tree/binary-tree.d.ts +10 -0
  12. package/dist/types/data-structures/binary-tree/red-black-tree.d.ts +7 -1
  13. package/dist/types/data-structures/graph/abstract-graph.d.ts +44 -0
  14. package/dist/types/data-structures/graph/directed-graph.d.ts +1 -0
  15. package/dist/types/data-structures/graph/undirected-graph.d.ts +14 -0
  16. package/dist/types/data-structures/queue/deque.d.ts +41 -1
  17. package/dist/types/types/data-structures/queue/deque.d.ts +6 -0
  18. package/dist/umd/avl-tree-typed.js +151 -64
  19. package/dist/umd/avl-tree-typed.js.map +1 -1
  20. package/dist/umd/avl-tree-typed.min.js +3 -3
  21. package/dist/umd/avl-tree-typed.min.js.map +1 -1
  22. package/package.json +2 -2
  23. package/src/common/error.ts +60 -0
  24. package/src/common/index.ts +2 -0
  25. package/src/data-structures/base/iterable-element-base.ts +3 -2
  26. package/src/data-structures/binary-tree/binary-indexed-tree.ts +6 -5
  27. package/src/data-structures/binary-tree/binary-tree.ts +113 -42
  28. package/src/data-structures/binary-tree/bst.ts +11 -3
  29. package/src/data-structures/binary-tree/red-black-tree.ts +20 -0
  30. package/src/data-structures/binary-tree/tree-map.ts +8 -7
  31. package/src/data-structures/binary-tree/tree-multi-map.ts +4 -4
  32. package/src/data-structures/binary-tree/tree-multi-set.ts +5 -4
  33. package/src/data-structures/binary-tree/tree-set.ts +7 -6
  34. package/src/data-structures/graph/abstract-graph.ts +106 -1
  35. package/src/data-structures/graph/directed-graph.ts +4 -0
  36. package/src/data-structures/graph/undirected-graph.ts +95 -0
  37. package/src/data-structures/hash/hash-map.ts +13 -2
  38. package/src/data-structures/heap/heap.ts +4 -3
  39. package/src/data-structures/heap/max-heap.ts +2 -3
  40. package/src/data-structures/matrix/matrix.ts +9 -10
  41. package/src/data-structures/priority-queue/max-priority-queue.ts +2 -3
  42. package/src/data-structures/queue/deque.ts +71 -3
  43. package/src/data-structures/trie/trie.ts +2 -1
  44. package/src/types/data-structures/queue/deque.ts +7 -0
  45. package/src/utils/utils.ts +4 -2
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Centralized error message templates.
3
+ * Keep using native Error/TypeError/RangeError — this only standardizes messages.
4
+ */
5
+ export declare const ERR: {
6
+ readonly indexOutOfRange: (index: number, min: number, max: number, ctx?: string) => string;
7
+ readonly invalidIndex: (ctx?: string) => string;
8
+ readonly invalidArgument: (reason: string, ctx?: string) => string;
9
+ readonly comparatorRequired: (ctx?: string) => string;
10
+ readonly invalidKey: (reason: string, ctx?: string) => string;
11
+ readonly notAFunction: (name: string, ctx?: string) => string;
12
+ readonly invalidEntry: (ctx?: string) => string;
13
+ readonly invalidNaN: (ctx?: string) => string;
14
+ readonly invalidDate: (ctx?: string) => string;
15
+ readonly reduceEmpty: (ctx?: string) => string;
16
+ readonly callbackReturnType: (expected: string, got: string, ctx?: string) => string;
17
+ readonly invalidOperation: (reason: string, ctx?: string) => string;
18
+ readonly matrixDimensionMismatch: (op: string) => string;
19
+ readonly matrixSingular: () => string;
20
+ readonly matrixNotSquare: () => string;
21
+ readonly matrixNotRectangular: () => string;
22
+ readonly matrixRowMismatch: (expected: number, got: number) => string;
23
+ };
@@ -1,3 +1,4 @@
1
+ export { ERR } from './error';
1
2
  export declare enum DFSOperation {
2
3
  VISIT = 0,
3
4
  PROCESS = 1
@@ -735,6 +735,16 @@ export declare class BinaryTree<K = any, V = any, R = any> extends IterableEntry
735
735
  * @returns Layout information for this subtree.
736
736
  */
737
737
  protected _displayAux(node: BinaryTreeNode<K, V> | null | undefined, options: BinaryTreePrintOptions): NodeDisplayLayout;
738
+ protected static _buildNodeDisplay(line: string, width: number, left: NodeDisplayLayout, right: NodeDisplayLayout): NodeDisplayLayout;
739
+ /**
740
+ * Check if a node is a display leaf (empty, null, undefined, NIL, or real leaf).
741
+ */
742
+ protected _isDisplayLeaf(node: BinaryTreeNode<K, V> | null | undefined, options: BinaryTreePrintOptions): boolean;
743
+ protected _hasDisplayableChild(child: BinaryTreeNode<K, V> | null | undefined, options: BinaryTreePrintOptions): boolean;
744
+ /**
745
+ * Resolve a display leaf node to its layout.
746
+ */
747
+ protected _resolveDisplayLeaf(node: BinaryTreeNode<K, V> | null | undefined, options: BinaryTreePrintOptions, emptyDisplayLayout: NodeDisplayLayout): NodeDisplayLayout;
738
748
  /**
739
749
  * (Protected) Swaps the key/value properties of two nodes.
740
750
  * @remarks Time O(1)
@@ -5,7 +5,7 @@
5
5
  * @copyright Copyright (c) 2022 Pablo Zeng <zrwusa@gmail.com>
6
6
  * @license MIT License
7
7
  */
8
- import type { BinaryTreeDeleteResult, BTNRep, CRUD, EntryCallback, FamilyPosition, NodePredicate, RBTNColor, RedBlackTreeOptions } from '../../types';
8
+ import type { BinaryTreeDeleteResult, BTNRep, CRUD, EntryCallback, FamilyPosition, NodePredicate, RBTNColor, IterationType, RedBlackTreeOptions } from '../../types';
9
9
  import { BST } from './bst';
10
10
  import { IBinaryTree } from '../../interfaces';
11
11
  export declare class RedBlackTreeNode<K = any, V = any> {
@@ -375,6 +375,12 @@ export declare class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R
375
375
  * @param [thisArg] - See parameter type for details.
376
376
  * @returns A new RedBlackTree with mapped entries.
377
377
  */
378
+ /**
379
+ * Red-Black trees are self-balancing — `perfectlyBalance` rebuilds via
380
+ * sorted bulk insert, which naturally produces a balanced RBT.
381
+ * @remarks Time O(N), Space O(N)
382
+ */
383
+ perfectlyBalance(iterationType?: IterationType): boolean;
378
384
  map<MK = K, MV = V, MR = any>(callback: EntryCallback<K, V | undefined, [MK, MV]>, options?: Partial<RedBlackTreeOptions<MK, MV, MR>>, thisArg?: unknown): RedBlackTree<MK, MV, MR>;
379
385
  /**
380
386
  * (Internal) Create an empty instance of the same concrete tree type.
@@ -337,4 +337,48 @@ export declare abstract class AbstractGraph<V = any, E = any, VO extends Abstrac
337
337
  * @remarks Time O(1), Space O(1)
338
338
  */
339
339
  protected _getVertexKey(vertexOrKey: VO | VertexKey): VertexKey;
340
+ /**
341
+ * The edge connector string used in visual output.
342
+ * Override in subclasses (e.g., '--' for undirected, '->' for directed).
343
+ */
344
+ protected get _edgeConnector(): string;
345
+ /**
346
+ * Generate a text-based visual representation of the graph.
347
+ *
348
+ * **Adjacency list format:**
349
+ * ```
350
+ * Graph (5 vertices, 6 edges):
351
+ * A -> B (1), C (2)
352
+ * B -> D (3)
353
+ * C -> (no outgoing edges)
354
+ * D -> A (1)
355
+ * E (isolated)
356
+ * ```
357
+ *
358
+ * @param options - Optional display settings.
359
+ * @param options.showWeight - Whether to show edge weights (default: true).
360
+ * @returns The visual string.
361
+ */
362
+ toVisual(options?: {
363
+ showWeight?: boolean;
364
+ }): string;
365
+ /**
366
+ * Generate DOT language representation for Graphviz.
367
+ *
368
+ * @param options - Optional display settings.
369
+ * @param options.name - Graph name (default: 'G').
370
+ * @param options.showWeight - Whether to label edges with weight (default: true).
371
+ * @returns DOT format string.
372
+ */
373
+ toDot(options?: {
374
+ name?: string;
375
+ showWeight?: boolean;
376
+ }): string;
377
+ /**
378
+ * Print the graph to console.
379
+ * @param options - Display settings passed to `toVisual`.
380
+ */
381
+ print(options?: {
382
+ showWeight?: boolean;
383
+ }): void;
340
384
  }
@@ -157,6 +157,7 @@ export declare class DirectedGraph<V = any, E = any, VO extends DirectedVertex<V
157
157
  * @remarks Time O(1), Space O(1)
158
158
  */
159
159
  constructor(options?: Partial<GraphOptions<V>>);
160
+ protected get _edgeConnector(): string;
160
161
  protected _outEdgeMap: Map<VO, EO[]>;
161
162
  get outEdgeMap(): Map<VO, EO[]>;
162
163
  set outEdgeMap(v: Map<VO, EO[]>);
@@ -313,6 +313,20 @@ export declare class UndirectedGraph<V = any, E = any, VO extends UndirectedVert
313
313
  bridges: EO[];
314
314
  cutVertices: VO[];
315
315
  };
316
+ /**
317
+ * Find biconnected components using edge-stack Tarjan variant.
318
+ * A biconnected component is a maximal biconnected subgraph.
319
+ * @returns Array of edge arrays, each representing a biconnected component.
320
+ * @remarks Time O(V + E), Space O(V + E)
321
+ */
322
+ getBiconnectedComponents(): EO[][];
323
+ /**
324
+ * Detect whether the graph contains a cycle.
325
+ * Uses DFS with parent tracking.
326
+ * @returns `true` if a cycle exists, `false` otherwise.
327
+ * @remarks Time O(V + E), Space O(V)
328
+ */
329
+ hasCycle(): boolean;
316
330
  /**
317
331
  * Get bridges discovered by `tarjan()`.
318
332
  * @returns Array of edges that are bridges.
@@ -143,7 +143,10 @@ export declare class Deque<E = any, R = any> extends LinearBase<E, R> {
143
143
  * @param [options] - Options such as bucketSize, toElementFn, and maxLen.
144
144
  * @returns New Deque instance.
145
145
  */
146
- constructor(elements?: IterableWithSizeOrLength<E> | IterableWithSizeOrLength<R>, options?: DequeOptions<E, R>);
146
+ constructor(elements?: IterableWithSizeOrLength<E>, options?: DequeOptions<E, R>);
147
+ constructor(elements: IterableWithSizeOrLength<R>, options: DequeOptions<E, R> & {
148
+ toElementFn: (rawElement: R) => E;
149
+ });
147
150
  protected _bucketSize: number;
148
151
  /**
149
152
  * Get the current bucket size.
@@ -151,6 +154,26 @@ export declare class Deque<E = any, R = any> extends LinearBase<E, R> {
151
154
  * @returns Bucket capacity per bucket.
152
155
  */
153
156
  get bucketSize(): number;
157
+ protected _autoCompactRatio: number;
158
+ /**
159
+ * Get the auto-compaction ratio.
160
+ * When `elements / (bucketCount * bucketSize)` drops below this ratio after
161
+ * enough shift/pop operations, the deque auto-compacts.
162
+ * @remarks Time O(1), Space O(1)
163
+ * @returns Current ratio threshold. 0 means auto-compact is disabled.
164
+ */
165
+ get autoCompactRatio(): number;
166
+ /**
167
+ * Set the auto-compaction ratio.
168
+ * @remarks Time O(1), Space O(1)
169
+ * @param value - Ratio in [0,1]. 0 disables auto-compact.
170
+ */
171
+ set autoCompactRatio(value: number);
172
+ /**
173
+ * Counter for shift/pop operations since last compaction check.
174
+ * Only checks ratio every `_bucketSize` operations to minimize overhead.
175
+ */
176
+ protected _compactCounter: number;
154
177
  protected _bucketFirst: number;
155
178
  /**
156
179
  * Get the index of the first bucket in use.
@@ -369,6 +392,23 @@ export declare class Deque<E = any, R = any> extends LinearBase<E, R> {
369
392
  * @remarks Time O(N), Space O(1)
370
393
  * @returns void
371
394
  */
395
+ /**
396
+ * (Protected) Trigger auto-compaction if space utilization drops below threshold.
397
+ * Only checks every `_bucketSize` operations to minimize hot-path overhead.
398
+ * Uses element-based ratio: `elements / (bucketCount * bucketSize)`.
399
+ */
400
+ protected _autoCompact(): void;
401
+ /**
402
+ * Compact the deque by removing unused buckets.
403
+ * @remarks Time O(N), Space O(1)
404
+ * @returns True if compaction was performed (bucket count reduced).
405
+ */
406
+ /**
407
+ * Compact the deque by removing unused buckets.
408
+ * @remarks Time O(N), Space O(1)
409
+ * @returns True if compaction was performed (bucket count reduced).
410
+ */
411
+ compact(): boolean;
372
412
  shrinkToFit(): void;
373
413
  /**
374
414
  * Deep clone this deque, preserving options.
@@ -1,4 +1,10 @@
1
1
  import { LinearBaseOptions } from '../base';
2
2
  export type DequeOptions<E, R> = {
3
3
  bucketSize?: number;
4
+ /**
5
+ * When the ratio of used buckets to total buckets falls below this threshold
6
+ * after a shift/pop, auto-compact is triggered. Set to 0 to disable.
7
+ * Default: 0.5 (compact when less than half the buckets are in use).
8
+ */
9
+ autoCompactRatio?: number;
4
10
  } & LinearBaseOptions<E, R>;
@@ -82,6 +82,47 @@ var avlTreeTyped = (() => {
82
82
  return (...args) => trampoline(fn(...args));
83
83
  }
84
84
 
85
+ // src/common/error.ts
86
+ var ERR = {
87
+ // Range / index
88
+ indexOutOfRange: (index, min, max, ctx) => `${ctx ? ctx + ": " : ""}Index ${index} is out of range [${min}, ${max}].`,
89
+ invalidIndex: (ctx) => `${ctx ? ctx + ": " : ""}Index must be an integer.`,
90
+ // Type / argument
91
+ invalidArgument: (reason, ctx) => `${ctx ? ctx + ": " : ""}${reason}`,
92
+ comparatorRequired: (ctx) => `${ctx ? ctx + ": " : ""}Comparator is required for non-number/non-string/non-Date keys.`,
93
+ invalidKey: (reason, ctx) => `${ctx ? ctx + ": " : ""}${reason}`,
94
+ notAFunction: (name, ctx) => `${ctx ? ctx + ": " : ""}${name} must be a function.`,
95
+ invalidEntry: (ctx) => `${ctx ? ctx + ": " : ""}Each entry must be a [key, value] tuple.`,
96
+ invalidNaN: (ctx) => `${ctx ? ctx + ": " : ""}NaN is not a valid key.`,
97
+ invalidDate: (ctx) => `${ctx ? ctx + ": " : ""}Invalid Date key.`,
98
+ reduceEmpty: (ctx) => `${ctx ? ctx + ": " : ""}Reduce of empty structure with no initial value.`,
99
+ callbackReturnType: (expected, got, ctx) => `${ctx ? ctx + ": " : ""}Callback must return ${expected}; got ${got}.`,
100
+ // State / operation
101
+ invalidOperation: (reason, ctx) => `${ctx ? ctx + ": " : ""}${reason}`,
102
+ // Matrix
103
+ matrixDimensionMismatch: (op) => `Matrix: Dimensions must be compatible for ${op}.`,
104
+ matrixSingular: () => "Matrix: Singular matrix, inverse does not exist.",
105
+ matrixNotSquare: () => "Matrix: Must be square for inversion.",
106
+ matrixNotRectangular: () => "Matrix: Must be rectangular for transposition.",
107
+ matrixRowMismatch: (expected, got) => `Matrix: Expected row length ${expected}, but got ${got}.`
108
+ };
109
+
110
+ // src/common/index.ts
111
+ var Range = class {
112
+ constructor(low, high, includeLow = true, includeHigh = true) {
113
+ this.low = low;
114
+ this.high = high;
115
+ this.includeLow = includeLow;
116
+ this.includeHigh = includeHigh;
117
+ }
118
+ // Determine whether a key is within the range
119
+ isInRange(key, comparator) {
120
+ const lowCheck = this.includeLow ? comparator(key, this.low) >= 0 : comparator(key, this.low) > 0;
121
+ const highCheck = this.includeHigh ? comparator(key, this.high) <= 0 : comparator(key, this.high) < 0;
122
+ return lowCheck && highCheck;
123
+ }
124
+ };
125
+
85
126
  // src/data-structures/base/iterable-element-base.ts
86
127
  var IterableElementBase = class {
87
128
  /**
@@ -104,7 +145,7 @@ var avlTreeTyped = (() => {
104
145
  if (options) {
105
146
  const { toElementFn } = options;
106
147
  if (typeof toElementFn === "function") this._toElementFn = toElementFn;
107
- else if (toElementFn) throw new TypeError("toElementFn must be a function type");
148
+ else if (toElementFn) throw new TypeError(ERR.notAFunction("toElementFn"));
108
149
  }
109
150
  }
110
151
  /**
@@ -260,7 +301,7 @@ var avlTreeTyped = (() => {
260
301
  acc = initialValue;
261
302
  } else {
262
303
  const first = iter.next();
263
- if (first.done) throw new TypeError("Reduce of empty structure with no initial value");
304
+ if (first.done) throw new TypeError(ERR.reduceEmpty());
264
305
  acc = first.value;
265
306
  index = 1;
266
307
  }
@@ -1038,22 +1079,6 @@ var avlTreeTyped = (() => {
1038
1079
  }
1039
1080
  };
1040
1081
 
1041
- // src/common/index.ts
1042
- var Range = class {
1043
- constructor(low, high, includeLow = true, includeHigh = true) {
1044
- this.low = low;
1045
- this.high = high;
1046
- this.includeLow = includeLow;
1047
- this.includeHigh = includeHigh;
1048
- }
1049
- // Determine whether a key is within the range
1050
- isInRange(key, comparator) {
1051
- const lowCheck = this.includeLow ? comparator(key, this.low) >= 0 : comparator(key, this.low) > 0;
1052
- const highCheck = this.includeHigh ? comparator(key, this.high) <= 0 : comparator(key, this.high) < 0;
1053
- return lowCheck && highCheck;
1054
- }
1055
- };
1056
-
1057
1082
  // src/data-structures/binary-tree/binary-tree.ts
1058
1083
  var BinaryTreeNode = class {
1059
1084
  /**
@@ -1189,7 +1214,7 @@ var avlTreeTyped = (() => {
1189
1214
  return "MAL_NODE";
1190
1215
  }
1191
1216
  };
1192
- var BinaryTree = class extends IterableEntryBase {
1217
+ var BinaryTree = class _BinaryTree extends IterableEntryBase {
1193
1218
  /**
1194
1219
  * Creates an instance of BinaryTree.
1195
1220
  * @remarks Time O(N * M), where N is the number of items in `keysNodesEntriesOrRaws` and M is the tree size at insertion time (due to O(M) `set` operation). Space O(N) for storing the nodes.
@@ -1224,7 +1249,7 @@ var avlTreeTyped = (() => {
1224
1249
  if (isMapMode !== void 0) this._isMapMode = isMapMode;
1225
1250
  if (isDuplicate !== void 0) this._isDuplicate = isDuplicate;
1226
1251
  if (typeof toEntryFn === "function") this._toEntryFn = toEntryFn;
1227
- else if (toEntryFn) throw TypeError("toEntryFn must be a function type");
1252
+ else if (toEntryFn) throw new TypeError(ERR.notAFunction("toEntryFn", "BinaryTree"));
1228
1253
  }
1229
1254
  if (keysNodesEntriesOrRaws) this.setMany(keysNodesEntriesOrRaws);
1230
1255
  }
@@ -1452,7 +1477,7 @@ var avlTreeTyped = (() => {
1452
1477
  if (!this._root) {
1453
1478
  this._setRoot(newNode);
1454
1479
  if (this._isMapMode && newNode !== null && newNode !== void 0) this._store.set(newNode.key, newNode);
1455
- this._size = 1;
1480
+ if (newNode !== null) this._size = 1;
1456
1481
  return true;
1457
1482
  }
1458
1483
  const queue = new Queue([this._root]);
@@ -1484,7 +1509,7 @@ var avlTreeTyped = (() => {
1484
1509
  potentialParent.right = newNode;
1485
1510
  }
1486
1511
  if (this._isMapMode && newNode !== null && newNode !== void 0) this._store.set(newNode.key, newNode);
1487
- this._size++;
1512
+ if (newNode !== null) this._size++;
1488
1513
  return true;
1489
1514
  }
1490
1515
  return false;
@@ -2053,7 +2078,7 @@ var avlTreeTyped = (() => {
2053
2078
  }
2054
2079
  /**
2055
2080
  * Finds all leaf nodes in the tree.
2056
- * @remarks Time O(N), visits every node. Space O(H) for recursive stack or O(N) for iterative queue.
2081
+ * @remarks Time O(N), visits every node. Space O(H) for recursive or iterative stack.
2057
2082
  *
2058
2083
  * @template C - The type of the callback function.
2059
2084
  * @param [callback=this._DEFAULT_NODE_CALLBACK] - Function to call on each leaf node.
@@ -2076,15 +2101,15 @@ var avlTreeTyped = (() => {
2076
2101
  };
2077
2102
  dfs(startNode);
2078
2103
  } else {
2079
- const queue = new Queue([startNode]);
2080
- while (queue.length > 0) {
2081
- const cur = queue.shift();
2104
+ const stack = [startNode];
2105
+ while (stack.length > 0) {
2106
+ const cur = stack.pop();
2082
2107
  if (this.isRealNode(cur)) {
2083
2108
  if (this.isLeaf(cur)) {
2084
2109
  leaves.push(callback(cur));
2085
2110
  }
2086
- if (this.isRealNode(cur.left)) queue.push(cur.left);
2087
- if (this.isRealNode(cur.right)) queue.push(cur.right);
2111
+ if (this.isRealNode(cur.right)) stack.push(cur.right);
2112
+ if (this.isRealNode(cur.left)) stack.push(cur.left);
2088
2113
  }
2089
2114
  }
2090
2115
  }
@@ -2540,42 +2565,98 @@ var avlTreeTyped = (() => {
2540
2565
  _displayAux(node, options) {
2541
2566
  const { isShowNull, isShowUndefined, isShowRedBlackNIL } = options;
2542
2567
  const emptyDisplayLayout = [["\u2500"], 1, 0, 0];
2543
- if (node === null && !isShowNull) {
2544
- return emptyDisplayLayout;
2545
- } else if (node === void 0 && !isShowUndefined) {
2546
- return emptyDisplayLayout;
2547
- } else if (this.isNIL(node) && !isShowRedBlackNIL) {
2548
- return emptyDisplayLayout;
2549
- } else if (node !== null && node !== void 0) {
2550
- const key = node.key, line = this.isNIL(node) ? "S" : String(key), width = line.length;
2551
- return _buildNodeDisplay(
2552
- line,
2553
- width,
2554
- this._displayAux(node.left, options),
2555
- this._displayAux(node.right, options)
2556
- );
2557
- } else {
2558
- const line = node === void 0 ? "U" : "N", width = line.length;
2559
- return _buildNodeDisplay(line, width, [[""], 1, 0, 0], [[""], 1, 0, 0]);
2560
- }
2561
- function _buildNodeDisplay(line, width, left, right) {
2562
- const [leftLines, leftWidth, leftHeight, leftMiddle] = left;
2563
- const [rightLines, rightWidth, rightHeight, rightMiddle] = right;
2564
- const firstLine = " ".repeat(Math.max(0, leftMiddle + 1)) + "_".repeat(Math.max(0, leftWidth - leftMiddle - 1)) + line + "_".repeat(Math.max(0, rightMiddle)) + " ".repeat(Math.max(0, rightWidth - rightMiddle));
2565
- const secondLine = (leftHeight > 0 ? " ".repeat(leftMiddle) + "/" + " ".repeat(leftWidth - leftMiddle - 1) : " ".repeat(leftWidth)) + " ".repeat(width) + (rightHeight > 0 ? " ".repeat(rightMiddle) + "\\" + " ".repeat(rightWidth - rightMiddle - 1) : " ".repeat(rightWidth));
2566
- const mergedLines = [firstLine, secondLine];
2567
- for (let i = 0; i < Math.max(leftHeight, rightHeight); i++) {
2568
- const leftLine = i < leftHeight ? leftLines[i] : " ".repeat(leftWidth);
2569
- const rightLine = i < rightHeight ? rightLines[i] : " ".repeat(rightWidth);
2570
- mergedLines.push(leftLine + " ".repeat(width) + rightLine);
2568
+ const newFrame = (n) => ({
2569
+ node: n,
2570
+ stage: 0,
2571
+ leftLayout: emptyDisplayLayout,
2572
+ rightLayout: emptyDisplayLayout
2573
+ });
2574
+ const stack = [newFrame(node)];
2575
+ let result = emptyDisplayLayout;
2576
+ const setChildResult = (layout) => {
2577
+ if (stack.length === 0) {
2578
+ result = layout;
2579
+ return;
2580
+ }
2581
+ const parent = stack[stack.length - 1];
2582
+ if (parent.stage === 1) parent.leftLayout = layout;
2583
+ else parent.rightLayout = layout;
2584
+ };
2585
+ while (stack.length > 0) {
2586
+ const frame = stack[stack.length - 1];
2587
+ const cur = frame.node;
2588
+ if (frame.stage === 0) {
2589
+ if (this._isDisplayLeaf(cur, options)) {
2590
+ stack.pop();
2591
+ const layout = this._resolveDisplayLeaf(cur, options, emptyDisplayLayout);
2592
+ setChildResult(layout);
2593
+ continue;
2594
+ }
2595
+ frame.stage = 1;
2596
+ stack.push(newFrame(cur.left));
2597
+ } else if (frame.stage === 1) {
2598
+ frame.stage = 2;
2599
+ stack.push(newFrame(cur.right));
2600
+ } else {
2601
+ stack.pop();
2602
+ const line = this.isNIL(cur) ? "S" : String(cur.key);
2603
+ const layout = _BinaryTree._buildNodeDisplay(line, line.length, frame.leftLayout, frame.rightLayout);
2604
+ setChildResult(layout);
2571
2605
  }
2572
- return [
2573
- mergedLines,
2574
- leftWidth + width + rightWidth,
2575
- Math.max(leftHeight, rightHeight) + 2,
2576
- leftWidth + Math.floor(width / 2)
2577
- ];
2578
2606
  }
2607
+ return result;
2608
+ }
2609
+ static _buildNodeDisplay(line, width, left, right) {
2610
+ const [leftLines, leftWidth, leftHeight, leftMiddle] = left;
2611
+ const [rightLines, rightWidth, rightHeight, rightMiddle] = right;
2612
+ const firstLine = " ".repeat(Math.max(0, leftMiddle + 1)) + "_".repeat(Math.max(0, leftWidth - leftMiddle - 1)) + line + "_".repeat(Math.max(0, rightMiddle)) + " ".repeat(Math.max(0, rightWidth - rightMiddle));
2613
+ const secondLine = (leftHeight > 0 ? " ".repeat(leftMiddle) + "/" + " ".repeat(leftWidth - leftMiddle - 1) : " ".repeat(leftWidth)) + " ".repeat(width) + (rightHeight > 0 ? " ".repeat(rightMiddle) + "\\" + " ".repeat(rightWidth - rightMiddle - 1) : " ".repeat(rightWidth));
2614
+ const mergedLines = [firstLine, secondLine];
2615
+ for (let i = 0; i < Math.max(leftHeight, rightHeight); i++) {
2616
+ const leftLine = i < leftHeight ? leftLines[i] : " ".repeat(leftWidth);
2617
+ const rightLine = i < rightHeight ? rightLines[i] : " ".repeat(rightWidth);
2618
+ mergedLines.push(leftLine + " ".repeat(width) + rightLine);
2619
+ }
2620
+ return [
2621
+ mergedLines,
2622
+ leftWidth + width + rightWidth,
2623
+ Math.max(leftHeight, rightHeight) + 2,
2624
+ leftWidth + Math.floor(width / 2)
2625
+ ];
2626
+ }
2627
+ /**
2628
+ * Check if a node is a display leaf (empty, null, undefined, NIL, or real leaf).
2629
+ */
2630
+ _isDisplayLeaf(node, options) {
2631
+ const { isShowNull, isShowUndefined, isShowRedBlackNIL } = options;
2632
+ if (node === null && !isShowNull) return true;
2633
+ if (node === void 0 && !isShowUndefined) return true;
2634
+ if (this.isNIL(node) && !isShowRedBlackNIL) return true;
2635
+ if (node === null || node === void 0) return true;
2636
+ const hasDisplayableLeft = this._hasDisplayableChild(node.left, options);
2637
+ const hasDisplayableRight = this._hasDisplayableChild(node.right, options);
2638
+ return !hasDisplayableLeft && !hasDisplayableRight;
2639
+ }
2640
+ _hasDisplayableChild(child, options) {
2641
+ if (child === null) return !!options.isShowNull;
2642
+ if (child === void 0) return !!options.isShowUndefined;
2643
+ if (this.isNIL(child)) return !!options.isShowRedBlackNIL;
2644
+ return true;
2645
+ }
2646
+ /**
2647
+ * Resolve a display leaf node to its layout.
2648
+ */
2649
+ _resolveDisplayLeaf(node, options, emptyDisplayLayout) {
2650
+ const { isShowNull, isShowUndefined, isShowRedBlackNIL } = options;
2651
+ if (node === null && !isShowNull) return emptyDisplayLayout;
2652
+ if (node === void 0 && !isShowUndefined) return emptyDisplayLayout;
2653
+ if (this.isNIL(node) && !isShowRedBlackNIL) return emptyDisplayLayout;
2654
+ if (node !== null && node !== void 0) {
2655
+ const line2 = this.isNIL(node) ? "S" : String(node.key);
2656
+ return _BinaryTree._buildNodeDisplay(line2, line2.length, emptyDisplayLayout, emptyDisplayLayout);
2657
+ }
2658
+ const line = node === void 0 ? "U" : "N";
2659
+ return _BinaryTree._buildNodeDisplay(line, line.length, [[""], 1, 0, 0], [[""], 1, 0, 0]);
2579
2660
  }
2580
2661
  /**
2581
2662
  * (Protected) Swaps the key/value properties of two nodes.
@@ -3577,9 +3658,15 @@ var avlTreeTyped = (() => {
3577
3658
  if (a < b) return -1;
3578
3659
  return 0;
3579
3660
  }
3661
+ if (a instanceof Date && b instanceof Date) {
3662
+ const ta = a.getTime();
3663
+ const tb = b.getTime();
3664
+ if (Number.isNaN(ta) || Number.isNaN(tb)) throw new TypeError(ERR.invalidDate("BST"));
3665
+ return ta > tb ? 1 : ta < tb ? -1 : 0;
3666
+ }
3580
3667
  if (typeof a === "object" || typeof b === "object") {
3581
- throw TypeError(
3582
- `When comparing object type keys, a custom comparator must be provided in the constructor's options!`
3668
+ throw new TypeError(
3669
+ ERR.comparatorRequired("BST")
3583
3670
  );
3584
3671
  }
3585
3672
  return 0;