red-black-tree-typed
Version:
7,947 lines • 225 kB
JavaScript
"use strict";
var redBlackTreeTyped = (() => {
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
// src/index.ts
var src_exports = {};
__export(src_exports, {
BST: () => BST,
BSTNode: () => BSTNode,
BinaryTree: () => BinaryTree,
BinaryTreeNode: () => BinaryTreeNode,
DFSOperation: () => DFSOperation,
ERR: () => ERR,
Range: () => Range,
RedBlackTree: () => RedBlackTree,
RedBlackTreeNode: () => RedBlackTreeNode,
raise: () => raise
});
// src/utils/utils.ts
function isPrimitiveComparable(value) {
const valueType = typeof value;
if (valueType === "number") return true;
return valueType === "bigint" || valueType === "string" || valueType === "boolean";
}
function tryObjectToPrimitive(obj) {
if (typeof obj.valueOf === "function") {
const valueOfResult = obj.valueOf();
if (valueOfResult !== obj) {
if (isPrimitiveComparable(valueOfResult)) return valueOfResult;
if (typeof valueOfResult === "object" && valueOfResult !== null) return tryObjectToPrimitive(valueOfResult);
}
}
if (typeof obj.toString === "function") {
const stringResult = obj.toString();
if (stringResult !== "[object Object]") return stringResult;
}
return null;
}
function isComparable(value, isForceObjectComparable = false) {
if (value === null || value === void 0) return false;
if (isPrimitiveComparable(value)) return true;
if (typeof value !== "object") return false;
if (value instanceof Date) return true;
if (isForceObjectComparable) return true;
const comparableValue = tryObjectToPrimitive(value);
if (comparableValue === null || comparableValue === void 0) return false;
return isPrimitiveComparable(comparableValue);
}
var makeTrampolineThunk = (computation) => ({
isThunk: true,
// Marker indicating this is a thunk
fn: computation
// The deferred computation function
});
var isTrampolineThunk = (value) => typeof value === "object" && // Must be an object
value !== null && // Must not be null
"isThunk" in value && // Must have the 'isThunk' property
value.isThunk;
function trampoline(initial) {
let current = initial;
while (isTrampolineThunk(current)) {
current = current.fn();
}
return current;
}
function makeTrampoline(fn) {
return (...args) => trampoline(fn(...args));
}
// src/common/error.ts
function raise(ErrorClass, message) {
throw new ErrorClass(message);
}
var ERR = {
// Range / index
indexOutOfRange: (index, min, max, ctx) => `${ctx ? ctx + ": " : ""}Index ${index} is out of range [${min}, ${max}].`,
invalidIndex: (ctx) => `${ctx ? ctx + ": " : ""}Index must be an integer.`,
// Type / argument
invalidArgument: (reason, ctx) => `${ctx ? ctx + ": " : ""}${reason}`,
comparatorRequired: (ctx) => `${ctx ? ctx + ": " : ""}Comparator is required for non-number/non-string/non-Date keys.`,
invalidKey: (reason, ctx) => `${ctx ? ctx + ": " : ""}${reason}`,
notAFunction: (name, ctx) => `${ctx ? ctx + ": " : ""}${name} must be a function.`,
invalidEntry: (ctx) => `${ctx ? ctx + ": " : ""}Each entry must be a [key, value] tuple.`,
invalidNaN: (ctx) => `${ctx ? ctx + ": " : ""}NaN is not a valid key.`,
invalidDate: (ctx) => `${ctx ? ctx + ": " : ""}Invalid Date key.`,
reduceEmpty: (ctx) => `${ctx ? ctx + ": " : ""}Reduce of empty structure with no initial value.`,
callbackReturnType: (expected, got, ctx) => `${ctx ? ctx + ": " : ""}Callback must return ${expected}; got ${got}.`,
// State / operation
invalidOperation: (reason, ctx) => `${ctx ? ctx + ": " : ""}${reason}`,
// Matrix
matrixDimensionMismatch: (op) => `Matrix: Dimensions must be compatible for ${op}.`,
matrixSingular: () => "Matrix: Singular matrix, inverse does not exist.",
matrixNotSquare: () => "Matrix: Must be square for inversion.",
matrixNotRectangular: () => "Matrix: Must be rectangular for transposition.",
matrixRowMismatch: (expected, got) => `Matrix: Expected row length ${expected}, but got ${got}.`,
// Order statistic
orderStatisticNotEnabled: (method, ctx) => `${ctx ? ctx + ": " : ""}${method}() requires enableOrderStatistic: true.`
};
// src/common/index.ts
var DFSOperation = /* @__PURE__ */ ((DFSOperation2) => {
DFSOperation2[DFSOperation2["VISIT"] = 0] = "VISIT";
DFSOperation2[DFSOperation2["PROCESS"] = 1] = "PROCESS";
return DFSOperation2;
})(DFSOperation || {});
var Range = class {
constructor(low, high, includeLow = true, includeHigh = true) {
this.low = low;
this.high = high;
this.includeLow = includeLow;
this.includeHigh = includeHigh;
}
// Determine whether a key is within the range
isInRange(key, comparator) {
const lowCheck = this.includeLow ? comparator(key, this.low) >= 0 : comparator(key, this.low) > 0;
const highCheck = this.includeHigh ? comparator(key, this.high) <= 0 : comparator(key, this.high) < 0;
return lowCheck && highCheck;
}
};
// src/data-structures/base/iterable-element-base.ts
var IterableElementBase = class {
/**
* Create a new iterable base.
*
* @param options Optional behavior overrides. When provided, a `toElementFn`
* is used to convert a raw element (`R`) into a public element (`E`).
*
* @remarks
* Time O(1), Space O(1).
*/
constructor(options) {
/**
* The converter used to transform a raw element (`R`) into a public element (`E`).
*
* @remarks
* Time O(1), Space O(1).
*/
__publicField(this, "_toElementFn");
if (options) {
const { toElementFn } = options;
if (typeof toElementFn === "function") this._toElementFn = toElementFn;
else if (toElementFn) raise(TypeError, "toElementFn must be a function type");
}
}
/**
* Exposes the current `toElementFn`, if configured.
*
* @returns The converter function or `undefined` when not set.
* @remarks
* Time O(1), Space O(1).
*/
get toElementFn() {
return this._toElementFn;
}
/**
* Returns an iterator over the structure's elements.
*
* @param args Optional iterator arguments forwarded to the internal iterator.
* @returns An `IterableIterator<E>` that yields the elements in traversal order.
*
* @remarks
* Producing the iterator is O(1); consuming the entire iterator is Time O(n) with O(1) extra space.
*/
*[Symbol.iterator](...args) {
yield* this._getIterator(...args);
}
/**
* Returns an iterator over the values (alias of the default iterator).
*
* @returns An `IterableIterator<E>` over all elements.
* @remarks
* Creating the iterator is O(1); full iteration is Time O(n), Space O(1).
*/
*values() {
for (const item of this) yield item;
}
/**
* Tests whether all elements satisfy the predicate.
*
* @template TReturn
* @param predicate Function invoked for each element with signature `(value, index, self)`.
* @param thisArg Optional `this` binding for the predicate.
* @returns `true` if every element passes; otherwise `false`.
*
* @remarks
* Time O(n) in the worst case; may exit early when the first failure is found. Space O(1).
*/
every(predicate, thisArg) {
let index = 0;
for (const item of this) {
if (thisArg === void 0) {
if (!predicate(item, index++, this)) return false;
} else {
const fn = predicate;
if (!fn.call(thisArg, item, index++, this)) return false;
}
}
return true;
}
/**
* Tests whether at least one element satisfies the predicate.
*
* @param predicate Function invoked for each element with signature `(value, index, self)`.
* @param thisArg Optional `this` binding for the predicate.
* @returns `true` if any element passes; otherwise `false`.
*
* @remarks
* Time O(n) in the worst case; may exit early on first success. Space O(1).
*/
some(predicate, thisArg) {
let index = 0;
for (const item of this) {
if (thisArg === void 0) {
if (predicate(item, index++, this)) return true;
} else {
const fn = predicate;
if (fn.call(thisArg, item, index++, this)) return true;
}
}
return false;
}
/**
* Invokes a callback for each element in iteration order.
*
* @param callbackfn Function invoked per element with signature `(value, index, self)`.
* @param thisArg Optional `this` binding for the callback.
* @returns `void`.
*
* @remarks
* Time O(n), Space O(1).
*/
forEach(callbackfn, thisArg) {
let index = 0;
for (const item of this) {
if (thisArg === void 0) {
callbackfn(item, index++, this);
} else {
const fn = callbackfn;
fn.call(thisArg, item, index++, this);
}
}
}
// Implementation signature
find(predicate, thisArg) {
let index = 0;
for (const item of this) {
if (thisArg === void 0) {
if (predicate(item, index++, this)) return item;
} else {
const fn = predicate;
if (fn.call(thisArg, item, index++, this)) return item;
}
}
return;
}
/**
* Checks whether a strictly-equal element exists in the structure.
*
* @param element The element to test with `===` equality.
* @returns `true` if an equal element is found; otherwise `false`.
*
* @remarks
* Time O(n) in the worst case. Space O(1).
*/
has(element) {
for (const ele of this) if (ele === element) return true;
return false;
}
/**
* Check whether a value exists (Array-compatible alias for `has`).
* @remarks Provided for familiarity when migrating from Array. Time O(n), Space O(1).
* @param element - Element to search for (uses `===`).
* @returns `true` if found.
*/
includes(element) {
return this.has(element);
}
/**
* Return an iterator of `[index, value]` pairs (Array-compatible).
* @remarks Provided for familiarity when migrating from Array. Time O(n), Space O(1) per step.
*/
*entries() {
let index = 0;
for (const value of this) {
yield [index++, value];
}
}
/**
* Return an iterator of numeric indices (Array-compatible).
* @remarks Provided for familiarity when migrating from Array. Time O(n), Space O(1) per step.
*/
*keys() {
let index = 0;
for (const _ of this) {
yield index++;
}
}
/**
* Reduces all elements to a single accumulated value.
*
* @overload
* @param callbackfn Reducer of signature `(acc, value, index, self) => nextAcc`. The first element is used as the initial accumulator.
* @returns The final accumulated value typed as `E`.
*
* @overload
* @param callbackfn Reducer of signature `(acc, value, index, self) => nextAcc`.
* @param initialValue The initial accumulator value of type `E`.
* @returns The final accumulated value typed as `E`.
*
* @overload
* @template U The accumulator type when it differs from `E`.
* @param callbackfn Reducer of signature `(acc: U, value, index, self) => U`.
* @param initialValue The initial accumulator value of type `U`.
* @returns The final accumulated value typed as `U`.
*
* @remarks
* Time O(n), Space O(1). Throws if called on an empty structure without `initialValue`.
*/
reduce(callbackfn, initialValue) {
let index = 0;
const iter = this[Symbol.iterator]();
let acc;
if (arguments.length >= 2) {
acc = initialValue;
} else {
const first = iter.next();
if (first.done) raise(TypeError, "Reduce of empty structure with no initial value");
acc = first.value;
index = 1;
}
for (const value of iter) {
acc = callbackfn(acc, value, index++, this);
}
return acc;
}
/**
* Materializes the elements into a new array.
*
* @returns A shallow array copy of the iteration order.
* @remarks
* Time O(n), Space O(n).
*/
toArray() {
return [...this];
}
/**
* Returns a representation of the structure suitable for quick visualization.
* Defaults to an array of elements; subclasses may override to provide richer visuals.
*
* @returns A visual representation (array by default).
* @remarks
* Time O(n), Space O(n).
*/
toVisual() {
return [...this];
}
/**
* Prints `toVisual()` to the console. Intended for quick debugging.
*
* @returns `void`.
* @remarks
* Time O(n) due to materialization, Space O(n) for the intermediate representation.
*/
print() {
console.log(this.toVisual());
}
};
// src/data-structures/base/linear-base.ts
var LinearBase = class _LinearBase extends IterableElementBase {
/**
* Construct a linear container with runtime options.
* @param options - `{ maxLen?, ... }` bounds/behavior options.
* @remarks Time O(1), Space O(1)
*/
constructor(options) {
super(options);
__publicField(this, "_maxLen", -1);
if (options) {
const { maxLen } = options;
if (typeof maxLen === "number" && maxLen > 0 && maxLen % 1 === 0) this._maxLen = maxLen;
}
}
/**
* Upper bound for length (if positive), or `-1` when unbounded.
* @returns Maximum allowed length.
* @remarks Time O(1), Space O(1)
*/
get maxLen() {
return this._maxLen;
}
/**
* First index of a value from the left.
* @param searchElement - Value to match.
* @param fromIndex - Start position (supports negative index).
* @returns Index or `-1` if not found.
* @remarks Time O(n), Space O(1)
*/
indexOf(searchElement, fromIndex = 0) {
if (this.length === 0) return -1;
if (fromIndex < 0) fromIndex = this.length + fromIndex;
if (fromIndex < 0) fromIndex = 0;
for (let i = fromIndex; i < this.length; i++) {
const element = this.at(i);
if (element === searchElement) return i;
}
return -1;
}
/**
* Last index of a value from the right.
* @param searchElement - Value to match.
* @param fromIndex - Start position (supports negative index).
* @returns Index or `-1` if not found.
* @remarks Time O(n), Space O(1)
*/
lastIndexOf(searchElement, fromIndex = this.length - 1) {
if (this.length === 0) return -1;
if (fromIndex >= this.length) fromIndex = this.length - 1;
if (fromIndex < 0) fromIndex = this.length + fromIndex;
for (let i = fromIndex; i >= 0; i--) {
const element = this.at(i);
if (element === searchElement) return i;
}
return -1;
}
/**
* Find the first index matching a predicate.
* @param predicate - `(element, index, self) => boolean`.
* @param thisArg - Optional `this` for callback.
* @returns Index or `-1`.
* @remarks Time O(n), Space O(1)
*/
findIndex(predicate, thisArg) {
for (let i = 0; i < this.length; i++) {
const item = this.at(i);
if (item !== void 0 && predicate.call(thisArg, item, i, this)) return i;
}
return -1;
}
/**
* Concatenate elements and/or containers.
* @param items - Elements or other containers.
* @returns New container with combined elements (`this` type).
* @remarks Time O(sum(length)), Space O(sum(length))
*/
concat(...items) {
const newList = this.clone();
for (const item of items) {
if (item instanceof _LinearBase) {
newList.pushMany(item);
} else {
newList.push(item);
}
}
return newList;
}
/**
* In-place stable order via array sort semantics.
* @param compareFn - Comparator `(a, b) => number`.
* @returns This container.
* @remarks Time O(n log n), Space O(n) (materializes to array temporarily)
*/
sort(compareFn) {
const arr = this.toArray();
arr.sort(compareFn);
this.clear();
for (const item of arr) this.push(item);
return this;
}
/**
* Remove and/or insert elements at a position (array-compatible).
* @param start - Start index (supports negative index).
* @param deleteCount - How many to remove.
* @param items - Elements to insert.
* @returns Removed elements as a new list (`this` type).
* @remarks Time O(n + m), Space O(min(n, m)) where `m = items.length`
*/
splice(start, deleteCount = 0, ...items) {
const removedList = this._createInstance();
start = start < 0 ? this.length + start : start;
start = Math.max(0, Math.min(start, this.length));
deleteCount = Math.max(0, Math.min(deleteCount, this.length - start));
for (let i = 0; i < deleteCount; i++) {
const removed = this.deleteAt(start);
if (removed !== void 0) {
removedList.push(removed);
}
}
for (let i = 0; i < items.length; i++) {
this.addAt(start + i, items[i]);
}
return removedList;
}
/**
* Join all elements into a string.
* @param separator - Separator string.
* @returns Concatenated string.
* @remarks Time O(n), Space O(n)
*/
join(separator = ",") {
return this.toArray().join(separator);
}
/**
* Snapshot elements into a reversed array.
* @returns New reversed array.
* @remarks Time O(n), Space O(n)
*/
toReversedArray() {
const array = [];
for (let i = this.length - 1; i >= 0; i--) {
array.push(this.at(i));
}
return array;
}
reduceRight(callbackfn, initialValue) {
let accumulator = initialValue != null ? initialValue : 0;
for (let i = this.length - 1; i >= 0; i--) {
accumulator = callbackfn(accumulator, this.at(i), i, this);
}
return accumulator;
}
/**
* Create a shallow copy of a subrange.
* @param start - Inclusive start (supports negative index).
* @param end - Exclusive end (supports negative index).
* @returns New list with the range (`this` type).
* @remarks Time O(n), Space O(n)
*/
slice(start = 0, end = this.length) {
start = start < 0 ? this.length + start : start;
end = end < 0 ? this.length + end : end;
const newList = this._createInstance();
for (let i = start; i < end; i++) {
newList.push(this.at(i));
}
return newList;
}
/**
* Fill a range with a value.
* @param value - Value to set.
* @param start - Inclusive start.
* @param end - Exclusive end.
* @returns This list.
* @remarks Time O(n), Space O(1)
*/
fill(value, start = 0, end = this.length) {
start = start < 0 ? this.length + start : start;
end = end < 0 ? this.length + end : end;
if (start < 0) start = 0;
if (end > this.length) end = this.length;
if (start >= end) return this;
for (let i = start; i < end; i++) {
this.setAt(i, value);
}
return this;
}
/**
* Return a new instance of the same type with elements in reverse order (non-mutating).
* @remarks Provided for familiarity when migrating from Array (ES2023 `toReversed`). Time O(n), Space O(n).
* @returns A new reversed instance.
*/
toReversed() {
const cloned = this.clone();
cloned.reverse();
return cloned;
}
};
// src/data-structures/base/iterable-entry-base.ts
var IterableEntryBase = class {
/**
* Default iterator yielding `[key, value]` entries.
* @returns Iterator of `[K, V]`.
* @remarks Time O(n) to iterate, Space O(1)
*/
*[Symbol.iterator](...args) {
yield* this._getIterator(...args);
}
/**
* Iterate over `[key, value]` pairs (may yield `undefined` values).
* @returns Iterator of `[K, V | undefined]`.
* @remarks Time O(n), Space O(1)
*/
*entries() {
for (const item of this) {
yield item;
}
}
/**
* Iterate over keys only.
* @returns Iterator of keys.
* @remarks Time O(n), Space O(1)
*/
*keys() {
for (const item of this) {
yield item[0];
}
}
/**
* Iterate over values only.
* @returns Iterator of values.
* @remarks Time O(n), Space O(1)
*/
*values() {
for (const item of this) {
yield item[1];
}
}
/**
* Test whether all entries satisfy the predicate.
* @param predicate - `(key, value, index, self) => boolean`.
* @param thisArg - Optional `this` for callback.
* @returns `true` if all pass; otherwise `false`.
* @remarks Time O(n), Space O(1)
*/
every(predicate, thisArg) {
let index = 0;
for (const item of this) {
if (!predicate.call(thisArg, item[1], item[0], index++, this)) {
return false;
}
}
return true;
}
/**
* Test whether any entry satisfies the predicate.
* @param predicate - `(key, value, index, self) => boolean`.
* @param thisArg - Optional `this` for callback.
* @returns `true` if any passes; otherwise `false`.
* @remarks Time O(n), Space O(1)
*/
some(predicate, thisArg) {
let index = 0;
for (const item of this) {
if (predicate.call(thisArg, item[1], item[0], index++, this)) {
return true;
}
}
return false;
}
/**
* Visit each entry, left-to-right.
* @param callbackfn - `(key, value, index, self) => void`.
* @param thisArg - Optional `this` for callback.
* @remarks Time O(n), Space O(1)
*/
forEach(callbackfn, thisArg) {
let index = 0;
for (const item of this) {
const [key, value] = item;
callbackfn.call(thisArg, value, key, index++, this);
}
}
/**
* Find the first entry that matches a predicate.
* @param callbackfn - `(key, value, index, self) => boolean`.
* @param thisArg - Optional `this` for callback.
* @returns Matching `[key, value]` or `undefined`.
* @remarks Time O(n), Space O(1)
*/
find(callbackfn, thisArg) {
let index = 0;
for (const item of this) {
const [key, value] = item;
if (callbackfn.call(thisArg, value, key, index++, this)) return item;
}
return;
}
/**
* Whether the given key exists.
* @param key - Key to test.
* @returns `true` if found; otherwise `false`.
* @remarks Time O(n) generic, Space O(1)
*/
has(key) {
for (const item of this) {
const [itemKey] = item;
if (itemKey === key) return true;
}
return false;
}
/**
* Whether there exists an entry with the given value.
* @param value - Value to test.
* @returns `true` if found; otherwise `false`.
* @remarks Time O(n), Space O(1)
*/
hasValue(value) {
for (const [, elementValue] of this) {
if (elementValue === value) return true;
}
return false;
}
/**
* Get the value under a key.
* @param key - Key to look up.
* @returns Value or `undefined`.
* @remarks Time O(n) generic, Space O(1)
*/
get(key) {
for (const item of this) {
const [itemKey, value] = item;
if (itemKey === key) return value;
}
return;
}
/**
* Reduce entries into a single accumulator.
* @param callbackfn - `(acc, value, key, index, self) => acc`.
* @param initialValue - Initial accumulator.
* @returns Final accumulator.
* @remarks Time O(n), Space O(1)
*/
reduce(callbackfn, initialValue) {
let accumulator = initialValue;
let index = 0;
for (const item of this) {
const [key, value] = item;
accumulator = callbackfn(accumulator, value, key, index++, this);
}
return accumulator;
}
/**
* Converts data structure to `[key, value]` pairs.
* @returns Array of entries.
* @remarks Time O(n), Space O(n)
*/
toArray() {
return [...this];
}
/**
* Visualize the iterable as an array of `[key, value]` pairs (or a custom string).
* @returns Array of entries (default) or a string.
* @remarks Time O(n), Space O(n)
*/
toVisual() {
return [...this];
}
/**
* Print a human-friendly representation to the console.
* @remarks Time O(n), Space O(n)
*/
print() {
console.log(this.toVisual());
}
};
// src/data-structures/queue/queue.ts
var Queue = class _Queue extends LinearBase {
/**
* Create a Queue and optionally bulk-insert elements.
* @remarks Time O(N), Space O(N)
* @param [elements] - Iterable of elements (or raw records if toElementFn is set).
* @param [options] - Options such as toElementFn, maxLen, and autoCompactRatio.
* @returns New Queue instance.
*/
constructor(elements = [], options) {
super(options);
__publicField(this, "_elements", []);
__publicField(this, "_offset", 0);
__publicField(this, "_autoCompactRatio", 0.5);
if (options) {
const { autoCompactRatio = 0.5 } = options;
this._autoCompactRatio = autoCompactRatio;
}
this.pushMany(elements);
}
/**
* Get the underlying array buffer.
* @remarks Time O(1), Space O(1)
* @returns Backing array of elements.
*/
get elements() {
return this._elements;
}
/**
* Get the current start offset into the array.
* @remarks Time O(1), Space O(1)
* @returns Zero-based offset.
*/
get offset() {
return this._offset;
}
/**
* Get the compaction threshold (offset/size).
* @remarks Time O(1), Space O(1)
* @returns Auto-compaction ratio in (0,1].
*/
get autoCompactRatio() {
return this._autoCompactRatio;
}
/**
* Set the compaction threshold.
* @remarks Time O(1), Space O(1)
* @param value - New ratio; compacts when offset/size exceeds this value.
* @returns void
*/
set autoCompactRatio(value) {
this._autoCompactRatio = value;
}
/**
* Get the number of elements currently in the queue.
* @remarks Time O(1), Space O(1)
* @returns Current length.
* @example
* // Track queue length
* const q = new Queue<number>();
* console.log(q.length); // 0;
* q.push(1);
* q.push(2);
* console.log(q.length); // 2;
*/
get length() {
return this.elements.length - this._offset;
}
/**
* Get the first element (front) without removing it.
* @remarks Time O(1), Space O(1)
* @returns Front element or undefined.
* @example
* // View the front element
* const q = new Queue<string>(['first', 'second', 'third']);
* console.log(q.first); // 'first';
* console.log(q.length); // 3;
*/
get first() {
return this.length > 0 ? this.elements[this._offset] : void 0;
}
/**
* Peek at the front element without removing it (alias for `first`).
* @remarks Time O(1), Space O(1)
* @returns Front element or undefined.
*/
peek() {
return this.first;
}
/**
* Get the last element (back) without removing it.
* @remarks Time O(1), Space O(1)
* @returns Back element or undefined.
*/
get last() {
return this.length > 0 ? this.elements[this.elements.length - 1] : void 0;
}
/**
* Create a queue from an array of elements.
* @remarks Time O(N), Space O(N)
* @template E
* @param elements - Array of elements to enqueue in order.
* @returns A new queue populated from the array.
*/
static fromArray(elements) {
return new _Queue(elements);
}
/**
* Check whether the queue is empty.
* @remarks Time O(1), Space O(1)
* @returns True if length is 0.
* @example
* // Queue for...of iteration and isEmpty check
* const queue = new Queue<string>(['A', 'B', 'C', 'D']);
*
* const elements: string[] = [];
* for (const item of queue) {
* elements.push(item);
* }
*
* // Verify all elements are iterated in order
* console.log(elements); // ['A', 'B', 'C', 'D'];
*
* // Process all elements
* while (queue.length > 0) {
* queue.shift();
* }
*
* console.log(queue.length); // 0;
*/
isEmpty() {
return this.length === 0;
}
/**
* Enqueue one element at the back.
* @remarks Time O(1), Space O(1)
* @param element - Element to enqueue.
* @returns True on success.
* @example
* // basic Queue creation and push operation
* // Create a simple Queue with initial values
* const queue = new Queue([1, 2, 3, 4, 5]);
*
* // Verify the queue maintains insertion order
* console.log([...queue]); // [1, 2, 3, 4, 5];
*
* // Check length
* console.log(queue.length); // 5;
*/
push(element) {
this.elements.push(element);
if (this._maxLen > 0 && this.length > this._maxLen) this.shift();
return true;
}
/**
* Enqueue many elements from an iterable.
* @remarks Time O(N), Space O(1)
* @param elements - Iterable of elements (or raw records if toElementFn is set).
* @returns Array of per-element success flags.
*/
pushMany(elements) {
const ans = [];
for (const el of elements) {
if (this.toElementFn) ans.push(this.push(this.toElementFn(el)));
else ans.push(this.push(el));
}
return ans;
}
/**
* Dequeue one element from the front (amortized via offset).
* @remarks Time O(1) amortized, Space O(1)
* @returns Removed element or undefined.
* @example
* // Queue shift and peek operations
* const queue = new Queue<number>([10, 20, 30, 40]);
*
* // Peek at the front element without removing it
* console.log(queue.first); // 10;
*
* // Remove and get the first element (FIFO)
* const first = queue.shift();
* console.log(first); // 10;
*
* // Verify remaining elements and length decreased
* console.log([...queue]); // [20, 30, 40];
* console.log(queue.length); // 3;
*/
shift() {
if (this.length === 0) return void 0;
const first = this.first;
this._offset += 1;
if (this.elements.length > 0 && this.offset / this.elements.length > this.autoCompactRatio) this.compact();
return first;
}
/**
* Delete the first occurrence of a specific element.
* @remarks Time O(N), Space O(1)
* @param element - Element to remove (strict equality via Object.is).
* @returns True if an element was removed.
* @example
* // Remove specific element
* const q = new Queue<number>([1, 2, 3, 2]);
* q.delete(2);
* console.log(q.length); // 3;
*/
delete(element) {
for (let i = this._offset; i < this.elements.length; i++) {
if (Object.is(this.elements[i], element)) {
this.elements.splice(i, 1);
return true;
}
}
return false;
}
/**
* Get the element at a given logical index.
* @remarks Time O(1), Space O(1)
* @param index - Zero-based index from the front.
* @returns Element or undefined.
* @example
* // Access element by index
* const q = new Queue<string>(['a', 'b', 'c']);
* console.log(q.at(0)); // 'a';
* console.log(q.at(2)); // 'c';
*/
at(index) {
if (index < 0 || index >= this.length) return void 0;
return this._elements[this._offset + index];
}
/**
* Delete the element at a given index.
* @remarks Time O(N), Space O(1)
* @param index - Zero-based index from the front.
* @returns Removed element or undefined.
*/
deleteAt(index) {
if (index < 0 || index >= this.length) return void 0;
const gi = this._offset + index;
const [deleted] = this.elements.splice(gi, 1);
return deleted;
}
/**
* Insert a new element at a given index.
* @remarks Time O(N), Space O(1)
* @param index - Zero-based index from the front.
* @param newElement - Element to insert.
* @returns True if inserted.
*/
addAt(index, newElement) {
if (index < 0 || index > this.length) return false;
this._elements.splice(this._offset + index, 0, newElement);
return true;
}
/**
* Replace the element at a given index.
* @remarks Time O(1), Space O(1)
* @param index - Zero-based index from the front.
* @param newElement - New element to set.
* @returns True if updated.
*/
setAt(index, newElement) {
if (index < 0 || index >= this.length) return false;
this._elements[this._offset + index] = newElement;
return true;
}
/**
* Delete the first element that satisfies a predicate.
* @remarks Time O(N), Space O(N)
* @param predicate - Function (value, index, queue) → boolean to decide deletion.
* @returns True if a match was removed.
*/
deleteWhere(predicate) {
for (let i = 0; i < this.length; i++) {
if (predicate(this._elements[this._offset + i], i, this)) {
this.deleteAt(i);
return true;
}
}
return false;
}
/**
* Reverse the queue in-place by compacting then reversing.
* @remarks Time O(N), Space O(N)
* @returns This queue.
*/
reverse() {
this._elements = this.elements.slice(this._offset).reverse();
this._offset = 0;
return this;
}
/**
* Remove all elements and reset offset.
* @remarks Time O(1), Space O(1)
* @returns void
* @example
* // Remove all elements
* const q = new Queue<number>([1, 2, 3]);
* q.clear();
* console.log(q.length); // 0;
*/
clear() {
this._elements = [];
this._offset = 0;
}
/**
* Compact storage by discarding consumed head elements.
* @remarks Time O(N), Space O(N)
* @returns True when compaction performed.
* @example
* // Reclaim unused memory
* const q = new Queue<number>([1, 2, 3, 4, 5]);
* q.shift();
* q.shift();
* q.compact();
* console.log(q.length); // 3;
*/
compact() {
this._elements = this.elements.slice(this._offset);
this._offset = 0;
return true;
}
/**
* Remove and/or insert elements at a position (array-like).
* @remarks Time O(N + M), Space O(M)
* @param start - Start index (clamped to [0, length]).
* @param [deleteCount] - Number of elements to remove (default 0).
* @param [items] - Elements to insert after `start`.
* @returns A new queue containing the removed elements (typed as `this`).
*/
splice(start, deleteCount = 0, ...items) {
start = Math.max(0, Math.min(start, this.length));
deleteCount = Math.max(0, Math.min(deleteCount, this.length - start));
const gi = this._offset + start;
const removedArray = this._elements.splice(gi, deleteCount, ...items);
if (this.elements.length > 0 && this.offset / this.elements.length > this.autoCompactRatio) this.compact();
const removed = this._createInstance({ toElementFn: this.toElementFn, maxLen: this._maxLen });
removed._setAutoCompactRatio(this._autoCompactRatio);
removed.pushMany(removedArray);
return removed;
}
/**
* Deep clone this queue and its parameters.
* @remarks Time O(N), Space O(N)
* @returns A new queue with the same content and options.
* @example
* // Create independent copy
* const q = new Queue<number>([1, 2, 3]);
* const copy = q.clone();
* copy.shift();
* console.log(q.length); // 3;
* console.log(copy.length); // 2;
*/
clone() {
const out = this._createInstance({ toElementFn: this.toElementFn, maxLen: this._maxLen });
out._setAutoCompactRatio(this._autoCompactRatio);
for (let i = this._offset; i < this.elements.length; i++) out.push(this.elements[i]);
return out;
}
/**
* Filter elements into a new queue of the same class.
* @remarks Time O(N), Space O(N)
* @param predicate - Predicate (element, index, queue) → boolean to keep element.
* @param [thisArg] - Value for `this` inside the predicate.
* @returns A new queue with kept elements.
* @example
* // Filter elements
* const q = new Queue<number>([1, 2, 3, 4, 5]);
* const evens = q.filter(x => x % 2 === 0);
* console.log(evens.length); // 2;
*/
filter(predicate, thisArg) {
const out = this._createInstance({ toElementFn: this.toElementFn, maxLen: this._maxLen });
out._setAutoCompactRatio(this._autoCompactRatio);
let index = 0;
for (const v of this) {
if (predicate.call(thisArg, v, index, this)) out.push(v);
index++;
}
return out;
}
/**
* Map each element to a new element in a possibly different-typed queue.
* @remarks Time O(N), Space O(N)
* @template EM
* @template RM
* @param callback - Mapping function (element, index, queue) → newElement.
* @param [options] - Options for the output queue (e.g., toElementFn, maxLen, autoCompactRatio).
* @param [thisArg] - Value for `this` inside the callback.
* @returns A new Queue with mapped elements.
* @example
* // Transform elements
* const q = new Queue<number>([1, 2, 3]);
* const doubled = q.map(x => x * 2);
* console.log(doubled.toArray()); // [2, 4, 6];
*/
map(callback, options, thisArg) {
var _a, _b;
const out = new this.constructor([], {
toElementFn: options == null ? void 0 : options.toElementFn,
maxLen: (_a = options == null ? void 0 : options.maxLen) != null ? _a : this._maxLen,
autoCompactRatio: (_b = options == null ? void 0 : options.autoCompactRatio) != null ? _b : this._autoCompactRatio
});
let index = 0;
for (const v of this)
out.push(thisArg === void 0 ? callback(v, index++, this) : callback.call(thisArg, v, index++, this));
return out;
}
/**
* Map each element to a new value of the same type.
* @remarks Time O(N), Space O(N)
* @param callback - Mapping function (element, index, queue) → element.
* @param [thisArg] - Value for `this` inside the callback.
* @returns A new queue with mapped elements (same element type).
*/
mapSame(callback, thisArg) {
var _a;
const Ctor = this.constructor;
const out = new Ctor([], {
toElementFn: this.toElementFn,
maxLen: this._maxLen,
autoCompactRatio: this._autoCompactRatio
});
(_a = out._setAutoCompactRatio) == null ? void 0 : _a.call(out, this._autoCompactRatio);
let index = 0;
for (const v of this) {
const mv = thisArg === void 0 ? callback(v, index++, this) : callback.call(thisArg, v, index++, this);
out.push(mv);
}
return out;
}
/**
* (Protected) Set the internal auto-compaction ratio.
* @remarks Time O(1), Space O(1)
* @param value - New ratio to assign.
* @returns void
*/
_setAutoCompactRatio(value) {
this._autoCompactRatio = value;
}
/**
* (Protected) Iterate elements from front to back.
* @remarks Time O(N), Space O(1)
* @returns Iterator of E.
*/
*_getIterator() {
for (let i = this._offset; i < this.elements.length; i++) yield this.elements[i];
}
/**
* (Protected) Iterate elements from back to front.
* @remarks Time O(N), Space O(1)
* @returns Iterator of E.
*/
*_getReverseIterator() {
for (let i = this.length - 1; i >= 0; i--) {
const cur = this.at(i);
if (cur !== void 0) yield cur;
}
}
/**
* (Protected) Create an empty instance of the same concrete class.
* @remarks Time O(1), Space O(1)
* @param [options] - Options forwarded to the constructor.
* @returns An empty like-kind queue instance.
*/
_createInstance(options) {
const Ctor = this.constructor;
return new Ctor([], options);
}
/**
* (Protected) Create a like-kind queue and seed it from an iterable.
* @remarks Time O(N), Space O(N)
* @template EM
* @template RM
* @param [elements] - Iterable used to seed the new queue.
* @param [options] - Options forwarded to the constructor.
* @returns A like-kind Queue instance.
*/
_createLike(elements = [], options) {
const Ctor = this.constructor;
return new Ctor(elements, options);
}
};
// src/data-structures/binary-tree/binary-tree.ts
var BinaryTreeNode = class {
/**
* Creates an instance of BinaryTreeNode.
* @remarks Time O(1), Space O(1)
*
* @param key - The key of the node.
* @param [value] - The value associated with the key.
*/
constructor(key, value) {
__publicField(this, "key");
__publicField(this, "value");
__publicField(this, "parent");
__publicField(this, "_left");
__publicField(this, "_right");
__publicField(this, "_height", 0);
__publicField(this, "_color", "BLACK");
__publicField(this, "_count", 1);
this.key = key;
this.value = value;
}
/**
* Gets the left child of the node.
* @remarks Time O(1), Space O(1)
*
* @returns The left child.
*/
get left() {
return this._left;
}
/**
* Sets the left child of the node and updates its parent reference.
* @remarks Time O(1), Space O(1)
*
* @param v - The node to set as the left child.
*/
set left(v) {
if (v) {
v.parent = this;
}
this._left = v;
}
/**
* Gets the right child of the node.
* @remarks Time O(1), Space O(1)
*
* @returns The right child.
*/
get right() {
return this._right;
}
/**
* Sets the right child of the node and updates its parent reference.
* @remarks Time O(1), Space O(1)
*
* @param v - The node to set as the right child.
*/
set right(v) {
if (v) {
v.parent = this;
}
this._right = v;
}
/**
* Gets the height of the node (used in self-balancing trees).
* @remarks Time O(1), Space O(1)
*
* @returns The height.
*/
get height() {
return this._height;
}
/**
* Sets the height of the node.
* @remarks Time O(1), Space O(1)
*
* @param value - The new height.
*/
set height(value) {
this._height = value;
}
/**
* Gets the color of the node (used in Red-Black trees).
* @remarks Time O(1), Space O(1)
*
* @returns The node's color.
*/
get color() {
return this._color;
}
/**
* Sets the color of the node.
* @remarks Time O(1), Space O(1)
*
* @param value - The new color.
*/
set color(value) {
this._color = value;
}
/**
* Gets the count of nodes in the subtree rooted at this node (used in order-statistic trees).
* @remarks Time O(1), Space O(1)
*
* @returns The subtree node count.
*/
get count() {
return this._count;
}
/**
* Sets the count of nodes in the subtree.
* @remarks Time O(1), Space O(1)
*
* @param value - The new count.
*/
set count(value) {
this._count = value;
}
/**
* Gets the position of the node relative to its parent.
* @remarks Time O(1), Space O(1)
*
* @returns The family position (e.g., 'ROOT', 'LEFT', 'RIGHT').
*/
get familyPosition() {
if (!this.parent) {
return this.left || this.right ? "ROOT" : "ISOLATED";
}
if (this.parent.left === this) {
return this.left || this.right ? "ROOT_LEFT" : "LEFT";
} else if (this.parent.right === this) {
return this.left || this.right ? "ROOT_RIGHT" : "RIGHT";
}
return "MAL_NODE";
}
};
var BinaryTree = class _BinaryTree extends IterableEntryBase {
/**
* Creates an instance of BinaryTree.
* @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.
*
* @param [keysNodesEntriesOrRaws=[]] - An iterable of items to set.
* @param [options] - Configuration options for the tree.
*/
constructor(keysNodesEntriesOrRaws = [], options) {
super();
__publicField(this, "iterationType", "ITERATIVE");
__publicField(this, "_isMapMode", true);
__publicField(this, "_isDuplicate", false);
// Map mode acceleration store:
// - isMapMode=false: unused
// - isMapMode=true: key -> node reference (O(1) has/getNode + fast get)
__publicField(this, "_store", /* @__PURE__ */ new Map());
__publicField(this, "_root");
__publicField(this, "_size", 0);
__publicField(this, "_NIL", new BinaryTreeNode(NaN));
__publicField(this, "_toEntryFn");
/**
* (Protected) Default callback function, returns the node's key.
* @remarks Time O(1)
*
* @param node - The node.
* @returns The node's key or undefined.
*/
__publicField(this, "_DEFAULT_NODE_CALLBACK", (node) => node == null ? void 0 : node.key);
if (options) {
const { iterationType, toEntryFn, isMapMode, isDuplicate } = options;
if (iterationType) this.iterationType = iterationType;
if (isMapMode !== void 0) this._isMapMode = isMapMode;
if (isDuplicate !== void 0) this._isDuplicate = isDuplicate;
if (typeof toEntryFn === "function") this._toEntryFn = toEntryFn;
else if (toEntryFn) raise(TypeError, ERR.notAFunction("toEntryFn", "BinaryTree"));
}
if (keysNodesEntriesOrRaws) this.setMany(keysNodesEntriesOrRaws);
}
/**
* Gets whether the tree is in Map mode.
* @remarks In Map mode (default), values are stored in an external Map, and nodes only hold keys. If false, values are stored directly on the nodes. Time O(1)
*
* @returns True if in Map mode, false otherwise.
*/
get isMapMode() {
return this._isMapMode;
}
/**
* Gets whether the tree allows duplicate keys.
* @remarks Time O(1)
*
* @returns True if duplicates are allowed, false otherwise.
*/
get isDuplicate() {
return this._isDuplicate;
}
/**
* Gets the external value store (used in Map mode).
* @remarks Time O(1)
*
* @returns The map storing key-value pairs.
*/
get store() {
return this._store;
}
/**
* Gets the root node of the tree.
* @remarks Time O(1)
*
* @returns The root node.
*/
get root() {
return this._root;
}
/**
* Gets the number of nodes in the tree.
* @remarks Time O(1)
*
* @returns The size of the tree.
*/
get size() {
return this._size;
}
/**
* Gets the sentinel NIL node (used in self-balancing trees like Red-Black Tree).
* @remarks Time O(1)
*
* @returns The NIL node.
*/
get NIL() {
return this._NIL;
}
/**
* Gets the function used to convert raw data objects (R) into [key, value] entries.
* @remarks Time O(1)
*
* @returns The conversion function.
*/
get toEntryFn() {
return this._toEntryFn;
}
/**
* (Protected) Creates a new node.
* @remarks Time O(1), Space O(1)
*
* @param key - The key for the new node.
* @param [value] - The value for the new node (used if not in Map mode).
* @returns The newly created node.
*/
createNode(key, value) {
return new BinaryTreeNode(key, value);
}
/**
* Creates a new, empty tree of the same type and configuration.
* @remarks Time O(1) (excluding options cloning), Space O(1)
*
* @param [options] - Optional overrides for the new tree's options.
* @returns A new, empty tree instance.
*/
createTree(options) {
return this._createInstance(options);
}
/**
* Ensures the input is a node. If it's a key or entry, it searches for the node.
* @remarks Time O(1) if a node is passed. O(N) if a key or entry is passed (due to `getNode` performing a full search). Space O(1) if iterative search, O(H) if recursive (where H is height, O(N) worst-case).
*
* @param keyNodeOrEntry - The item to resolve to a node.
* @param [iterationType=this.iterationType] - The traversal method to use if searching.
* @returns The resolved node, or null/undefined if not found or input is null/undefined.
*/
ensureNode(keyNodeOrEntry, iterationType = this.iterationType) {
if (keyNodeOrEntry === null) return null;
if (keyNodeOrEntry === void 0) return;
if (keyNodeOrEntry === this._NIL) return;
if (this.isNode(keyNodeOrEntry)) return keyNodeOrEntry;
if (this.isEntry(keyNodeOrEntry)) {
const key = keyNodeOrEntry[0];
if (key === null) return null;
if (key === void 0) return;
return this.getNode(key, this._root, iterationType);
}
return this.getNode(keyNodeOrEntry, this._root, iterationType);
}
/**
* Checks if the given item is a `BinaryTreeNode` instance.
* @remarks Time O(1), Space O(1)
*
* @param keyNodeOrEntry - The item to check.
* @returns True if it's a node, false otherwise.
*/
isNode(keyNodeOrEntry) {
return keyNodeOrEntry instanceof BinaryTreeNode;
}
/**
* Checks if the given item is a raw data object (R) that needs conversion via `toEntryFn`.
* @remarks Time O(1), Space O(1)
*
* @param keyNodeEntryOrRaw - The item to check.
* @returns True if it's a raw object, false otherwise.
*/
isRaw(keyNodeEntryOrRaw) {
return this._toEntryFn !== void 0 && typeof keyNodeEntryOrRaw === "object";
}
/**
* Checks if the given item is a "real" node (i.e., not null, undefined, or NIL).
* @remarks Time O(1), Space O(1)
*
* @param keyNodeOrEntry - The item to check.
* @returns True if it's a real node, false otherwise.
*/
isRealNode(keyNodeOrEntry) {
if (keyNodeOrEntry === this._NIL || keyNodeOrEntry === null || keyNodeOrEntry === void 0) return false;
return this.isNode(keyNodeOrEntry);
}
/**
* Checks if the given item is either a "real" node or null.
* @remarks Time O(1), Space O(1)
*
* @param keyNodeOrEntry - The item to check.
* @returns True if it's a real node or null, false otherwise.
*/
isRealNodeOrNull(keyNodeOrEntry) {
return keyNodeOrEntry === null || this.isRealNode(keyNodeOrEntry);
}
/**
* Checks if the given item is the sentinel NIL node.
* @remarks Time O(1), Space O(1)
*
* @param keyNodeOrEntry - The item to check.
* @returns True if it's the NIL node, false otherwise.
*/
isNIL(keyNodeOrEntry) {
return keyNodeOrEntry === this._NIL;
}
/**
* Checks if the given item is a `Range` object.
* @remarks Time O(1), Space O(1)
*
* @param keyNodeEntryOrPredicate - The item to check.
* @returns True if it's a Range, false otherwise.
*/
isRange(keyNodeEntryOrPredicate) {
return keyNodeEntryOrPredicate instanceof Range;
}
/**
* Checks if a node is a leaf (has no real children).
* @remarks Time O(N) if a key/entry is passed (due to `ensureNode`). O(1) if a node is passed. Space O(1) or O(H) (from `ensureNode`).
*
* @param keyNodeOrEntry - The node to check.
* @returns True if the node is a leaf, false otherwise.
*/
isLeaf(keyNodeOrEntry) {
keyNodeOrEntry = this.ensureNode(keyNodeOrEntry);
if (keyNodeOrEntry === void 0) return false;
if (keyNodeOrEntry === null) return true;
return !this.isRealNode(keyNodeOrEntry.left) && !this.isRealNode(keyNodeOrEntry.right);
}
/**
* Checks if the given item is a [key, value] entry pair.
* @remarks Time O(1), Space O(1)
*
* @param keyNodeOrEntry - The item to check.
* @returns True if it's an entry, false otherwise.
*/
isEntry(keyNodeOrEntry) {
return Array.isArray(keyNodeOrEntry) && keyNodeOrEntry.length === 2;
}
/**
* Checks if the given key is valid (comparable or null).
* @remarks Time O(1), Space O(1)
*
* @param key - The key to validate.
* @returns True if the key is valid, false otherwise.
*/
isValidKey(key) {
if (key === null) return true;
return isComparable(key);
}
/**
* Adds a new node to the tree.
* @remarks Time O(N) — level-order traversal to find an empty slot. Space O(N) for the BFS queue. BST/Red-Black Tree/AVL Tree subclasses override to O(log N).
*
* @param keyNodeOrEntry - The key, node, or entry to add.
* @returns True if the addition was successful, false otherwise.
* @example
* // Add a single node
* const tree = new BinaryTree<number>();
* tree.add(1);
* tree.add(2);
* tree.add(3);
* console.log(tree.size); // 3;
* console.log(tree.has(1)); // true;
*/
add(keyNodeOrEntry) {
return this.set(keyNodeOrEntry);
}
/**
* Adds or updates a new node to the tree.
* @remarks Time O(N) — level-order traversal to find an empty slot. Space O(N) for the BFS queue. BST/Red-Black Tree/AVL Tree subclasses override to O(log N).
*
* @param keyNodeOrEntry - The key, node, or entry to set or update.
* @param [value] - The value, if providing just a key.
* @returns True if the addition was successful, false otherwise.
* @example
* // basic BinaryTree creation and insertion
* // Create a BinaryTree with entries
* const entries: [number, string][] = [
* [6, 'six'],
* [1, 'one'],
* [2, 'two'],
* [7, 'seven'],
* [5, 'five'],
* [3, 'three'],
* [4, 'four'],
* [9, 'nine'],
* [8, 'eight']
* ];
*
* const tree = new BinaryTree(entries);
*
* // Verify size
* console.log(tree.size); // 9;
*
* // Add new element
* tree.set(10, 'ten');
* console.log(tree.size); // 10;
*/
set(keyNodeOrEntry, value) {
const [newNode] = this._keyValueNodeOrEntryToNodeAndValue(keyNodeOrEntry, value);
if (newNode === void 0) return false;
if (!this._root) {
this._setRoot(newNode);
if (this._isMapMode && newNode !== null && newNode !== void 0) this._store.set(newNode.key, newNode);
if (newNode !== null) this._size = 1;
return true;
}
const queue = new Queue([this._root]);
let potentialParent;
while (queue.length > 0) {
const cur = queue.shift();
if (!cur) continue;
if (!this._isDuplicate) {
if (newNode !== null && cur.key === newNode.key) {
this._replaceNode(cur, newNode);
if (this._isMapMode && newNode !== null) this._store.set(cur.key, newNode);
return true;
}
}
if (potentialParent === void 0 && (cur.left === void 0 || cur.right === void 0)) {
potentialParent = cur;
}
if (cur.left !== null) {
if (cur.left) queue.push(cur.left);
}
if (cur.right !== null) {
if (cur.right) queue.push(cur.right);
}
}
if (potentialParent) {
if (potentialParent.left === void 0) {
potentialParent.left = newNode;
} else if (potentialParent.right === void 0) {
potentialParent.right = newNode;
}
if (this._isMapMode && newNode !== null && newNode !== void 0) this._store.set(newNode.key, newNode);
if (newNode !== null) this._size++;
return true;
}
return false;
}
/**
* Adds multiple items to the tree.
* @remarks Time O(N * M), where N is the number of items to set and M is the size of the tree at insertion (due to O(M) `set` operation). Space O(M) (from `set`) + O(N) (for the `inserted` array).
*
* @param keysNodesEntriesOrRaws - An iterable of items to set.
* @returns An array of booleans indicating the success of each individual `set` operation.
* @example
* // Bulk add
* const tree = new BinaryTree<number>();
* tree.addMany([1, 2, 3, 4, 5]);
* console.log(tree.size); // 5;
*/
addMany(keysNodesEntriesOrRaws) {
return this.setMany(keysNodesEntriesOrRaws);
}
/**
* Adds or updates multiple items to the tree.
* @remarks Time O(N * M), where N is the number of items to set and M is the size of the tree at insertion (due to O(M) `set` operation). Space O(M) (from `set`) + O(N) (for the `inserted` array).
*
* @param keysNodesEntriesOrRaws - An iterable of items to set or update.
* @param [values] - An optional parallel iterable of values.
* @returns An array of booleans indicating the success of each individual `set` operation.
* @example
* // Set multiple entries
* const tree = new BinaryTree<number, string>();
* tree.setMany([[1, 'a'], [2, 'b'], [3, 'c']]);
* console.log(tree.size); // 3;
*/
setMany(keysNodesEntriesOrRaws, values) {
const inserted = [];
let valuesIterator;
if (values) {
valuesIterator = values[Symbol.iterator]();
}
for (let keyNodeEntryOrRaw of keysNodesEntriesOrRaws) {
let value = void 0;
if (valuesIterator) {
const valueResult = valuesIterator.next();
if (!valueResult.done) {
value = valueResult.value;
}
}
if (this.isRaw(keyNodeEntryOrRaw)) keyNodeEntryOrRaw = this._toEntryFn(keyNodeEntryOrRaw);
inserted.push(this.set(keyNodeEntryOrRaw, value));
}
return inserted;
}
/**
* Merges another tree into this one by seting all its nodes.
* @remarks Time O(N * M), same as `setMany`, where N is the size of `anotherTree` and M is the size of this tree. Space O(M) (from `set`).
*
* @param anotherTree - The tree to merge.
* @example
* // Combine trees
* const t1 = new BinaryTree<number>([1, 2]);
* const t2 = new BinaryTree<number>([3, 4]);
* t1.merge(t2);
* console.log(t1.size); // 4;
*/
merge(anotherTree) {
this.setMany(anotherTree, []);
}
/**
* Deletes a node from the tree (internal, returns balancing metadata).
* @remarks Time O(N) — O(N) to find the node + O(H) for predecessor swap. Space O(1). BST/Red-Black Tree/AVL Tree subclasses override to O(log N).
* @internal Used by AVL/BST subclasses that need balancing metadata after deletion.
*
* @param keyNodeEntryRawOrPredicate - The node to delete.
* @returns An array containing deletion results with balancing metadata.
*/
_deleteInternal(keyNodeEntryRawOrPredicate) {
const deletedResult = [];
if (!this._root) return deletedResult;
const curr = this.getNode(keyNodeEntryRawOrPredicate);
if (!curr) return deletedResult;
const parent = curr == null ? void 0 : curr.parent;
let needBalanced;
let orgCurrent = curr;
if (!curr.left && !curr.right && !parent) {
this._setRoot(void 0);
} else if (curr.left) {
const leftSubTreeRightMost = this.getRightMost((node) => node, curr.left);
if (leftSubTreeRightMost) {
const parentOfLeftSubTreeMax = leftSubTreeRightMost.parent;
orgCurrent = this._swapProperties(curr, leftSubTreeRightMost);
if (this._isMapMode) {
this._store.set(curr.key, curr);
this._store.set(leftSubTreeRightMost.key, leftSubTreeRightMost);
}
if (parentOfLeftSubTreeMax) {
if (parentOfLeftSubTreeMax.right === leftSubTreeRightMost)
parentOfLeftSubTreeMax.right = leftSubTreeRightMost.left;
else parentOfLeftSubTreeMax.left = leftSubTreeRightMost.left;
needBalanced = parentOfLeftSubTreeMax;
}
}
} else if (parent) {
const { familyPosition: fp } = curr;
if (fp === "LEFT" || fp === "ROOT_LEFT") {
parent.left = curr.right;
} else if (fp === "RIGHT" || fp === "ROOT_RIGHT") {
parent.right = curr.right;
}
needBalanced = parent;
} else {
this._setRoot(curr.right);
curr.right = void 0;
}
this._size = this._size - 1;
deletedResult.push({ deleted: orgCurrent, needBalanced });
if (this._isMapMode && orgCurrent) this._store.delete(orgCurrent.key);
return deletedResult;
}
/**
* Deletes a node from the tree.
* @remarks Time O(N) — O(N) to find the node + O(H) for predecessor swap. Space O(1). BST/Red-Black Tree/AVL Tree subclasses override to O(log N).
*
* @param keyNodeEntryRawOrPredicate - The node to delete.
* @returns True if the node was found and deleted, false otherwise.
* @example
* // Remove a node
* const tree = new BinaryTree<number>([1, 2, 3, 4, 5]);
* tree.delete(3);
* console.log(tree.has(3)); // false;
* console.log(tree.size); // 4;
*/
delete(keyNodeEntryRawOrPredicate) {
return this._deleteInternal(keyNodeEntryRawOrPredicate).length > 0;
}
/**
* Searches the tree for nodes matching a predicate.
* @remarks Time O(N) — full DFS scan; may visit every node. Space O(H) for call/explicit stack (O(N) worst-case). BST subclasses with key search override to O(log N).
*
* @template C - The type of the callback function.
* @param keyNodeEntryOrPredicate - The key, node, entry, or predicate function to search for.
* @param [onlyOne=false] - If true, stops after finding the first match.
* @param [callback=this._DEFAULT_NODE_CALLBACK] - A function to call on matching nodes.
* @param [startNode=this._root] - The node to start the search from.
* @param [iterationType=this.iterationType] - Whether to use 'RECURSIVE' or 'ITERATIVE' search.
* @returns An array of results from the callback function for each matching node.
*/
search(keyNodeEntryOrPredicate, onlyOne = false, callback = this._DEFAULT_NODE_CALLBACK, startNode = this._root, iterationType = this.iterationType) {
if (keyNodeEntryOrPredicate === void 0) return [];
if (keyNodeEntryOrPredicate === null) return [];
startNode = this.ensureNode(startNode);
if (!startNode) return [];
const predicate = this._ensurePredicate(keyNodeEntryOrPredicate);
const ans = [];
if (iterationType === "RECURSIVE") {
const dfs = (cur) => {
if (predicate(cur)) {
ans.push(callback(cur));
if (onlyOne) return;
}
if (!this.isRealNode(cur.left) && !this.isRealNode(cur.right)) return;
if (this.isRealNode(cur.left)) dfs(cur.left);
if (this.isRealNode(cur.right)) dfs(cur.right);
};
dfs(startNode);
} else {
const stack = [startNode];
while (stack.length > 0) {
const cur = stack.pop();
if (this.isRealNode(cur)) {
if (predicate(cur)) {
ans.push(callback(cur));
if (onlyOne) return ans;
}
if (this.isRealNode(cur.left)) stack.push(cur.left);
if (this.isRealNode(cur.right)) stack.push(cur.right);
}
}
}
return ans;
}
getNodes(keyNodeEntryOrPredicate, onlyOne = false, startNode = this._root, iterationType = this.iterationType) {
return this.search(keyNodeEntryOrPredicate, onlyOne, (node) => node, startNode, iterationType);
}
/**
* Gets the first node matching a predicate.
* @remarks Time O(N) via `search`. Space O(H) or O(N). BST/Red-Black Tree/AVL Tree subclasses override to O(log N) for key lookups.
*
* @param keyNodeEntryOrPredicate - The key, node, entry, or predicate function to search for.
* @param [startNode=this._root] - The node to start the search from.
* @param [iterationType=this.iterationType] - The traversal method.
* @returns The first matching node, or undefined if not found.
* @example
* // Get node by key
* const tree = new BinaryTree<number, string>([[1, 'root'], [2, 'child']]);
* console.log(tree.getNode(2)?.value); // 'child';
*/
getNode(keyNodeEntryOrPredicate, startNode = this._root, iterationType = this.iterationType) {
if (this._isMapMode && keyNodeEntryOrPredicate !== null && keyNodeEntryOrPredicate !== void 0) {
if (!this._isPredicate(keyNodeEntryOrPredicate)) {
const key = this._extractKey(keyNodeEntryOrPredicate);
if (key === null || key === void 0) return;
return this._store.get(key);
}
}
return this.search(keyNodeEntryOrPredicate, true, (node) => node, startNode, iterationType)[0];
}
/**
* Gets the value associated with a key.
* @remarks Time O(1) in Map mode, O(N) otherwise (via `getNode`). Space O(1) in Map mode, O(H) or O(N) otherwise. BST subclasses override non-Map-mode to O(log N).
*
* @param keyNodeEntryOrPredicate - The key, node, or entry to get the value for.
* @param [startNode=this._root] - The node to start searching from (if not in Map mode).
* @param [iterationType=this.iterationType] - The traversal method (if not in Map mode).
* @returns The associated value, or undefined.
* @example
* // Retrieve value by key
* const tree = new BinaryTree<number, string>([[1, 'root'], [2, 'left'], [3, 'right']]);
* console.log(tree.get(2)); // 'left';
* console.log(tree.get(99)); // undefined;
*/
get(keyNodeEntryOrPredicate, startNode = this._root, iterationType = this.iterationType) {
var _a, _b;
if (this._isMapMode) {
const key = this._extractKey(keyNodeEntryOrPredicate);
if (key === null || key === void 0) return;
return (_a = this._store.get(key)) == null ? void 0 : _a.value;
}
return (_b = this.getNode(keyNodeEntryOrPredicate, startNode, iterationType)) == null ? void 0 : _b.value;
}
has(keyNodeEntryOrPredicate, startNode = this._root, iterationType = this.iterationType) {
if (this._isMapMode && keyNodeEntryOrPredicate !== void 0 && keyNodeEntryOrPredicate !== null) {
if (!this._isPredicate(keyNodeEntryOrPredicate)) {
const key = this._extractKey(keyNodeEntryOrPredicate);
if (key === null || key === void 0) return false;
return this._store.has(key);
}
}
return this.search(keyNodeEntryOrPredicate, true, (node) => node, startNode, iterationType).length > 0;
}
/**
* Clears the tree of all nodes and values.
* @remarks Time O(N) if in Map mode (due to `_store.clear()`), O(1) otherwise. Space O(1)
* @example
* // Remove all nodes
* const tree = new BinaryTree<number>([1, 2, 3]);
* tree.clear();
* console.log(tree.isEmpty()); // true;
*/
clear() {
this._clearNodes();
if (this._isMapMode) this._clearValues();
}
/**
* Checks if the tree is empty.
* @remarks Time O(1), Space O(1)
*
* @returns True if the tree has no nodes, false otherwise.
* @example
* // Check empty
* console.log(new BinaryTree().isEmpty()); // true;
*/
isEmpty() {
return this._size === 0;
}
/**
* Checks if the tree is perfectly balanced.
* @remarks A tree is perfectly balanced if the difference between min and max height is at most 1. Time O(N), as it requires two full traversals (`getMinHeight` and `getHeight`). Space O(H) or O(N) (from height calculation).
*
* @param [startNode=this._root] - The node to start checking from.
* @returns True if perfectly balanced, false otherwise.
*/
isPerfectlyBalanced(startNode = this._root) {
return this.getMinHeight(startNode) + 1 >= this.getHeight(startNode);
}
/**
* Checks if the tree is a valid Binary Search Tree (BST).
* @remarks Time O(N), as it must visit every node. Space O(H) for the call stack (recursive) or explicit stack (iterative), where H is the tree height (O(N) worst-case).
*
* @param [startNode=this._root] - The node to start checking from.
* @param [iterationType=this.iterationType] - The traversal method.
* @returns True if it's a valid BST, false otherwise.
* @example
* // Check BST property
* const tree = new BinaryTree<number>([1, 2, 3]);
* // BinaryTree doesn't guarantee BST order
* console.log(typeof tree.isBST()); // 'boolean';
*/
isBST(startNode = this._root, iterationType = this.iterationType) {
const startNodeSired = this.ensureNode(startNode);
if (!startNodeSired) return true;
if (iterationType === "RECURSIVE") {
const dfs = (cur, min, max) => {
if (!this.isRealNode(cur)) return true;
const numKey = Number(cur.key);
if (numKey <= min || numKey >= max) return false;
return dfs(cur.left, min, numKey) && dfs(cur.right, numKey, max);
};
const isStandardBST = dfs(startNodeSired, Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER);
const isInverseBST = dfs(startNodeSired, Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER);
return isStandardBST || isInverseBST;
} else {
const checkBST = (checkMax = false) => {
const stack = [];
let prev = checkMax ? Number.MAX_SAFE_INTEGER : Number.MIN_SAFE_INTEGER;
let curr = startNodeSired;
while (this.isRealNode(curr) || stack.length > 0) {
while (this.isRealNode(curr)) {
stack.push(curr);
curr = curr.left;
}
curr = stack.pop();
const numKey = Number(curr.key);
if (!this.isRealNode(curr) || !checkMax && prev >= numKey || checkMax && prev <= numKey) return false;
prev = numKey;
curr = curr.right;
}
return true;
};
const isStandardBST = checkBST();
const isInverseBST = checkBST(true);
return isStandardBST || isInverseBST;
}
}
/**
* Gets the depth of a node (distance from `startNode`).
* @remarks Time O(H), where H is the depth of the `dist` node relative to `startNode`. O(N) worst-case. Space O(1).
*
* @param dist - The node to find the depth of.
* @param [startNode=this._root] - The node to measure depth from (defaults to root).
* @returns The depth (0 if `dist` is `startNode`).
* @example
* // Get depth of a node
* const tree = new BinaryTree<number>([1, 2, 3, 4, 5]);
* const node = tree.getNode(4);
* console.log(tree.getDepth(node!)); // 2;
*/
getDepth(dist, startNode = this._root) {
let distEnsured = this.ensureNode(dist);
const beginRootEnsured = this.ensureNode(startNode);
let depth = 0;
while (distEnsured == null ? void 0 : distEnsured.parent) {
if (distEnsured === beginRootEnsured) {
return depth;
}
depth++;
distEnsured = distEnsured.parent;
}
return depth;
}
/**
* Gets the maximum height of the tree (longest path from startNode to a leaf).
* @remarks Time O(N), as it must visit every node. Space O(H) for recursive stack (O(N) worst-case) or O(N) for iterative stack (storing node + depth).
*
* @param [startNode=this._root] - The node to start measuring from.
* @param [iterationType=this.iterationType] - The traversal method.
* @returns The height ( -1 for an empty tree, 0 for a single-node tree).
* @example
* // Get tree height
* const tree = new BinaryTree<number>([1, 2, 3, 4, 5]);
* console.log(tree.getHeight()); // 2;
*/
getHeight(startNode = this._root, iterationType = this.iterationType) {
startNode = this.ensureNode(startNode);
if (!this.isRealNode(startNode)) return -1;
if (iterationType === "RECURSIVE") {
const _getMaxHeight = (cur) => {
if (!this.isRealNode(cur)) return -1;
const leftHeight = _getMaxHeight(cur.left);
const rightHeight = _getMaxHeight(cur.right);
return Math.max(leftHeight, rightHeight) + 1;
};
return _getMaxHeight(startNode);
} else {
const stack = [{ node: startNode, depth: 0 }];
let maxHeight = 0;
while (stack.length > 0) {
const { node, depth } = stack.pop();
if (this.isRealNode(node.left)) stack.push({ node: node.left, depth: depth + 1 });
if (this.isRealNode(node.right)) stack.push({ node: node.right, depth: depth + 1 });
maxHeight = Math.max(maxHeight, depth);
}
return maxHeight;
}
}
/**
* Gets the minimum height of the tree (shortest path from startNode to a leaf).
* @remarks Time O(N), as it must visit every node. Space O(H) for recursive stack (O(N) worst-case) or O(N) for iterative (due to `depths` Map).
*
* @param [startNode=this._root] - The node to start measuring from.
* @param [iterationType=this.iterationType] - The traversal method.
* @returns The minimum height (-1 for empty, 0 for single node).
*/
getMinHeight(startNode = this._root, iterationType = this.iterationType) {
startNode = this.ensureNode(startNode);
if (!startNode) return -1;
if (iterationType === "RECURSIVE") {
const _getMinHeight = (cur) => {
if (!this.isRealNode(cur)) return 0;
if (!this.isRealNode(cur.left) && !this.isRealNode(cur.right)) return 0;
const leftMinHeight = _getMinHeight(cur.left);
const rightMinHeight = _getMinHeight(cur.right);
return Math.min(leftMinHeight, rightMinHeight) + 1;
};
return _getMinHeight(startNode);
} else {
const stack = [];
let node = startNode, last = null;
const depths = /* @__PURE__ */ new Map();
while (stack.length > 0 || node) {
if (this.isRealNode(node)) {
stack.push(node);
node = node.left;
} else {
node = stack[stack.length - 1];
if (!this.isRealNode(node.right) || last === node.right) {
node = stack.pop();
if (this.isRealNode(node)) {
const leftMinHeight = this.isRealNode(node.left) ? depths.get(node.left) : -1;
const rightMinHeight = this.isRealNode(node.right) ? depths.get(node.right) : -1;
depths.set(node, 1 + Math.min(leftMinHeight, rightMinHeight));
last = node;
node = null;
}
} else node = node.right;
}
}
return depths.get(startNode);
}
}
/**
* Gets the path from a given node up to the root.
* @remarks Time O(H), where H is the depth of the `beginNode`. O(N) worst-case. Space O(H) for the result array.
*
* @template C - The type of the callback function.
* @param beginNode - The node to start the path from.
* @param [callback=this._DEFAULT_NODE_CALLBACK] - A function to call on each node in the path.
* @param [isReverse=false] - If true, returns the path from root-to-node.
* @returns An array of callback results.
*/
getPathToRoot(beginNode, callback = this._DEFAULT_NODE_CALLBACK, isReverse = false) {
const result = [];
let beginNodeEnsured = this.ensureNode(beginNode);
if (!beginNodeEnsured) return result;
while (beginNodeEnsured.parent) {
result.push(callback(beginNodeEnsured));
beginNodeEnsured = beginNodeEnsured.parent;
}
result.push(callback(beginNodeEnsured));
return isReverse ? result.reverse() : result;
}
/**
* Finds the leftmost node in a subtree (the node with the smallest key in a BST).
* @remarks Time O(H), where H is the height of the left spine. O(N) worst-case. Space O(H) for recursive/trampoline stack.
*
* @template C - The type of the callback function.
* @param [callback=this._DEFAULT_NODE_CALLBACK] - A function to call on the leftmost node.
* @param [startNode=this._root] - The subtree root to search from.
* @param [iterationType=this.iterationType] - The traversal method.
* @returns The callback result for the leftmost node.
*/
getLeftMost(callback = this._DEFAULT_NODE_CALLBACK, startNode = this._root, iterationType = this.iterationType) {
if (this.isNIL(startNode)) return callback(void 0);
const ensuredStartNode = this.ensureNode(startNode);
if (!this.isRealNode(ensuredStartNode)) return callback(void 0);
if (iterationType === "RECURSIVE") {
const dfs = (cur) => {
const { left } = cur;
if (!this.isRealNode(left)) return cur;
return dfs(left);
};
return callback(dfs(ensuredStartNode));
} else {
const dfs = makeTrampoline((cur) => {
const { left } = cur;
if (!this.isRealNode(left)) return cur;
return makeTrampolineThunk(() => dfs(left));
});
return callback(dfs(ensuredStartNode));
}
}
/**
* Finds the rightmost node in a subtree (the node with the largest key in a BST).
* @remarks Time O(H), where H is the height of the right spine. O(N) worst-case. Space O(H) for recursive/trampoline stack.
*
* @template C - The type of the callback function.
* @param [callback=this._DEFAULT_NODE_CALLBACK] - A function to call on the rightmost node.
* @param [startNode=this._root] - The subtree root to search from.
* @param [iterationType=this.iterationType] - The traversal method.
* @returns The callback result for the rightmost node.
*/
getRightMost(callback = this._DEFAULT_NODE_CALLBACK, startNode = this._root, iterationType = this.iterationType) {
if (this.isNIL(startNode)) return callback(void 0);
startNode = this.ensureNode(startNode);
if (!startNode) return callback(void 0);
if (iterationType === "RECURSIVE") {
const dfs = (cur) => {
const { right } = cur;
if (!this.isRealNode(right)) return cur;
return dfs(right);
};
return callback(dfs(startNode));
} else {
const dfs = makeTrampoline((cur) => {
const { right } = cur;
if (!this.isRealNode(right)) return cur;
return makeTrampolineThunk(() => dfs(right));
});
return callback(dfs(startNode));
}
}
/**
* Gets the Morris traversal predecessor (rightmost node in the left subtree, or node itself).
* @remarks This is primarily a helper for Morris traversal. Time O(H), where H is the height of the left subtree. O(N) worst-case. Space O(1).
*
* @param node - The node to find the predecessor for.
* @returns The Morris predecessor.
*/
getPredecessor(node) {
if (this.isRealNode(node.left)) {
let predecessor = node.left;
while (!this.isRealNode(predecessor) || this.isRealNode(predecessor.right) && predecessor.right !== node) {
if (this.isRealNode(predecessor)) {
predecessor = predecessor.right;
}
}
return predecessor;
} else {
return node;
}
}
/**
* Gets the in-order successor of a node in a BST.
* @remarks Time O(H), where H is the tree height. O(N) worst-case. Space O(H) (due to `getLeftMost` stack).
*
* @param [x] - The node to find the successor of.
* @returns The successor node, or null/undefined if none exists.
*/
getSuccessor(x) {
x = this.ensureNode(x);
if (!this.isRealNode(x)) return void 0;
if (this.isRealNode(x.right)) {
return this.getLeftMost((node) => node, x.right);
}
let y = x.parent;
while (this.isRealNode(y) && x === y.right) {
x = y;
y = y.parent;
}
return y;
}
/**
* Performs a Depth-First Search (DFS) traversal.
* @remarks Time O(N), visits every node. Space O(H) for the call/explicit stack. O(N) worst-case.
*
* @template C - The type of the callback function.
* @param [callback=this._DEFAULT_NODE_CALLBACK] - Function to call on each node.
* @param [pattern='IN'] - The traversal order ('IN', 'PRE', 'POST').
* @param [onlyOne=false] - If true, stops after the first callback.
* @param [startNode=this._root] - The node to start from.
* @param [iterationType=this.iterationType] - The traversal method.
* @param [includeNull=false] - If true, includes null nodes in the traversal.
* @returns An array of callback results.
*/
dfs(callback = this._DEFAULT_NODE_CALLBACK, pattern = "IN", onlyOne = false, startNode = this._root, iterationType = this.iterationType, includeNull = false) {
startNode = this.ensureNode(startNode);
if (!startNode) return [];
return this._dfs(callback, pattern, onlyOne, startNode, iterationType, includeNull);
}
/**
* Performs a Breadth-First Search (BFS) or Level-Order traversal.
* @remarks Time O(N), visits every node. Space O(N) in the worst case for the queue (e.g., a full last level).
*
* @template C - The type of the callback function.
* @param [callback=this._DEFAULT_NODE_CALLBACK] - Function to call on each node.
* @param [startNode=this._root] - The node to start from.
* @param [iterationType=this.iterationType] - The traversal method ('RECURSIVE' BFS is less common but supported here).
* @param [includeNull=false] - If true, includes null nodes in the traversal.
* @returns An array of callback results.
*/
bfs(callback = this._DEFAULT_NODE_CALLBACK, startNode = this._root, iterationType = this.iterationType, includeNull = false) {
startNode = this.ensureNode(startNode);
if (!startNode) return [];
const ans = [];
if (iterationType === "RECURSIVE") {
const queue = new Queue([
startNode
]);
const dfs = (level) => {
if (queue.length === 0) return;
const current = queue.shift();
ans.push(callback(current));
if (includeNull) {
if (current && this.isRealNodeOrNull(current.left)) queue.push(current.left);
if (current && this.isRealNodeOrNull(current.right)) queue.push(current.right);
} else {
if (this.isRealNode(current.left)) queue.push(current.left);
if (this.isRealNode(current.right)) queue.push(current.right);
}
dfs(level + 1);
};
dfs(0);
} else {
const queue = new Queue([startNode]);
while (queue.length > 0) {
const levelSize = queue.length;
for (let i = 0; i < levelSize; i++) {
const current = queue.shift();
ans.push(callback(current));
if (includeNull) {
if (current && this.isRealNodeOrNull(current.left)) queue.push(current.left);
if (current && this.isRealNodeOrNull(current.right)) queue.push(current.right);
} else {
if (this.isRealNode(current.left)) queue.push(current.left);
if (this.isRealNode(current.right)) queue.push(current.right);
}
}
}
}
return ans;
}
/**
* Finds all leaf nodes in the tree.
* @remarks Time O(N), visits every node. Space O(H) for recursive or iterative stack.
*
* @template C - The type of the callback function.
* @param [callback=this._DEFAULT_NODE_CALLBACK] - Function to call on each leaf node.
* @param [startNode=this._root] - The node to start from.
* @param [iterationType=this.iterationType] - The traversal method.
* @returns An array of callback results.
*/
leaves(callback = this._DEFAULT_NODE_CALLBACK, startNode = this._root, iterationType = this.iterationType) {
startNode = this.ensureNode(startNode);
const leaves = [];
if (!this.isRealNode(startNode)) return [];
if (iterationType === "RECURSIVE") {
const dfs = (cur) => {
if (this.isLeaf(cur)) {
leaves.push(callback(cur));
}
if (!this.isRealNode(cur.left) && !this.isRealNode(cur.right)) return;
if (this.isRealNode(cur.left)) dfs(cur.left);
if (this.isRealNode(cur.right)) dfs(cur.right);
};
dfs(startNode);
} else {
const stack = [startNode];
while (stack.length > 0) {
const cur = stack.pop();
if (this.isRealNode(cur)) {
if (this.isLeaf(cur)) {
leaves.push(callback(cur));
}
if (this.isRealNode(cur.right)) stack.push(cur.right);
if (this.isRealNode(cur.left)) stack.push(cur.left);
}
}
}
return leaves;
}
/**
* Returns a 2D array of nodes, grouped by level.
* @remarks Time O(N), visits every node. Space O(N) for the result array and the queue/stack.
*
* @template C - The type of the callback function.
* @param [callback=this._DEFAULT_NODE_CALLBACK] - Function to call on each node.
* @param [startNode=this._root] - The node to start from.
* @param [iterationType=this.iterationType] - The traversal method.
* @param [includeNull=false] - If true, includes null nodes.
* @returns A 2D array of callback results.
*/
listLevels(callback = this._DEFAULT_NODE_CALLBACK, startNode = this._root, iterationType = this.iterationType, includeNull = false) {
startNode = this.ensureNode(startNode);
const levelsNodes = [];
if (!startNode) return levelsNodes;
if (iterationType === "RECURSIVE") {
const _recursive = (node, level) => {
if (!levelsNodes[level]) levelsNodes[level] = [];
levelsNodes[level].push(callback(node));
if (includeNull) {
if (node && this.isRealNodeOrNull(node.left)) _recursive(node.left, level + 1);
if (node && this.isRealNodeOrNull(node.right)) _recursive(node.right, level + 1);
} else {
if (node && node.left) _recursive(node.left, level + 1);
if (node && node.right) _recursive(node.right, level + 1);
}
};
_recursive(startNode, 0);
} else {
const stack = [[startNode, 0]];
while (stack.length > 0) {
const head = stack.pop();
const [node, level] = head;
if (!levelsNodes[level]) levelsNodes[level] = [];
levelsNodes[level].push(callback(node));
if (includeNull) {
if (node && this.isRealNodeOrNull(node.right)) stack.push([node.right, level + 1]);
if (node && this.isRealNodeOrNull(node.left)) stack.push([node.left, level + 1]);
} else {
if (node && node.right) stack.push([node.right, level + 1]);
if (node && node.left) stack.push([node.left, level + 1]);
}
}
}
return levelsNodes;
}
/**
* Performs a Morris (threaded) traversal.
* @remarks This traversal uses O(1) extra space (excluding the result array) by temporarily modifying the tree's right child pointers. Time O(N), as each node is visited a constant number of times. Space O(1) (excluding the `ans` array).
*
* @template C - The type of the callback function.
* @param [callback=this._DEFAULT_NODE_CALLBACK] - Function to call on each node.
* @param [pattern='IN'] - The traversal order ('IN', 'PRE', 'POST').
* @param [startNode=this._root] - The node to start from.
* @returns An array of callback results.
*/
morris(callback = this._DEFAULT_NODE_CALLBACK, pattern = "IN", startNode = this._root) {
startNode = this.ensureNode(startNode);
if (!startNode) return [];
const ans = [];
let cur = startNode;
const _reverseEdge = (node) => {
let pre = null;
let next = null;
while (node) {
next = node.right;
node.right = pre;
pre = node;
node = next;
}
return pre;
};
const _printEdge = (node) => {
const tail = _reverseEdge(node);
let cur2 = tail;
while (cur2) {
ans.push(callback(cur2));
cur2 = cur2.right;
}
_reverseEdge(tail);
};
switch (pattern) {
case "IN":
while (cur) {
if (cur.left) {
const predecessor = this.getPredecessor(cur);
if (!predecessor.right) {
predecessor.right = cur;
cur = cur.left;
continue;
} else {
predecessor.right = null;
}
}
ans.push(callback(cur));
cur = cur.right;
}
break;
case "PRE":
while (cur) {
if (cur.left) {
const predecessor = this.getPredecessor(cur);
if (!predecessor.right) {
predecessor.right = cur;
ans.push(callback(cur));
cur = cur.left;
continue;
} else {
predecessor.right = null;
}
} else {
ans.push(callback(cur));
}
cur = cur.right;
}
break;
case "POST":
while (cur) {
if (cur.left) {
const predecessor = this.getPredecessor(cur);
if (predecessor.right === null) {
predecessor.right = cur;
cur = cur.left;
continue;
} else {
predecessor.right = null;
_printEdge(cur.left);
}
}
cur = cur.right;
}
_printEdge(startNode);
break;
}
return ans;
}
/**
* Clones the tree.
* @remarks Time O(N * M), where N is the number of nodes and M is the tree size during insertion (due to `bfs` + `set`, and `set` is O(M)). Space O(N) for the new tree and the BFS queue.
*
* @returns A new, cloned instance of the tree.
* @example
* // Deep copy
* const tree = new BinaryTree<number>([1, 2, 3]);
* const copy = tree.clone();
* copy.delete(1);
* console.log(tree.has(1)); // true;
*/
clone() {
const out = this._createInstance();
this._clone(out);
return out;
}
/**
* Creates a new tree containing only the entries that satisfy the predicate.
* @remarks Time O(N * M), where N is nodes in this tree, and M is size of the new tree during insertion (O(N) iteration + O(M) `set` for each item). Space O(N) for the new tree.
*
* @param predicate - A function to test each [key, value] pair.
* @param [thisArg] - `this` context for the predicate.
* @returns A new, filtered tree.
* @example
* // Filter nodes by condition
* const tree = new BinaryTree<number>([1, 2, 3, 4]);
* const result = tree.filter((_, key) => key > 2);
* console.log(result.size); // 2;
*/
filter(predicate, thisArg) {
const out = this._createInstance();
let i = 0;
for (const [k, v] of this) if (predicate.call(thisArg, v, k, i++, this)) out.set([k, v]);
return out;
}
/**
* Creates a new tree by mapping each [key, value] pair to a new entry.
* @remarks Time O(N * M), where N is nodes in this tree, and M is size of the new tree during insertion. Space O(N) for the new tree.
*
* @template MK - New key type.
* @template MV - New value type.
* @template MR - New raw type.
* @param cb - A function to map each [key, value] pair.
* @param [options] - Options for the new tree.
* @param [thisArg] - `this` context for the callback.
* @returns A new, mapped tree.
* @example
* // Transform to new tree
* const tree = new BinaryTree<number, number>([[1, 10], [2, 20]]);
* const mapped = tree.map((v, key) => [key, (v ?? 0) + 1] as [number, number]);
* console.log([...mapped.values()]); // contains 11;
*/
map(cb, options, thisArg) {
const out = this._createLike([], options);
let i = 0;
for (const [k, v] of this) out.set(cb.call(thisArg, v, k, i++, this));
return out;
}
/**
* Generates a string representation of the tree for visualization.
* @remarks Time O(N), visits every node. Space O(N*H) or O(N^2) in the worst case, as the string width can grow significantly.
*
* @param [startNode=this._root] - The node to start printing from.
* @param [options] - Options to control the output (e.g., show nulls).
* @returns The string representation of the tree.
*/
toVisual(startNode = this._root, options) {
const opts = { isShowUndefined: false, isShowNull: true, isShowRedBlackNIL: false, ...options };
startNode = this.ensureNode(startNode);
let output = "";
if (!startNode) return output;
if (opts.isShowUndefined) output += `U for undefined
`;
if (opts.isShowNull) output += `N for null
`;
if (opts.isShowRedBlackNIL) output += `S for Sentinel Node(NIL)
`;
const display = (root) => {
const [lines] = this._displayAux(root, opts);
let paragraph = "";
for (const line of lines) {
paragraph += line + "\n";
}
output += paragraph;
};
display(startNode);
return output;
}
/**
* Prints a visual representation of the tree to the console.
* @remarks Time O(N) (via `toVisual`). Space O(N*H) or O(N^2) (via `toVisual`).
*
* @param [options] - Options to control the output.
* @param [startNode=this._root] - The node to start printing from.
* @example
* // Display tree
* const tree = new BinaryTree<number>([1, 2, 3]);
* expect(() => tree.print()).not.toThrow();
*/
print(options, startNode = this._root) {
console.log(this.toVisual(startNode, options));
}
/**
* (Protected) Core DFS implementation.
* @remarks Time O(N), visits every node satisfying predicates. Space O(H) for call/explicit stack. O(N) worst-case.
*
* @template C - Callback type.
* @param [callback=this._DEFAULT_NODE_CALLBACK] - Function to call on nodes.
* @param [pattern='IN'] - Traversal order.
* @param [onlyOne=false] - Stop after first match.
* @param [startNode=this._root] - Starting node.
* @param [iterationType=this.iterationType] - Traversal method.
* @param [includeNull=false] - Include nulls.
* @param [shouldVisitLeft] - Predicate to traverse left.
* @param [shouldVisitRight] - Predicate to traverse right.
* @param [shouldVisitRoot] - Predicate to visit root.
* @param [shouldProcessRoot] - Predicate to process root.
* @returns Array of callback results.
*/
_dfs(callback = this._DEFAULT_NODE_CALLBACK, pattern = "IN", onlyOne = false, startNode = this._root, iterationType = this.iterationType, includeNull = false, shouldVisitLeft = (node) => !!node, shouldVisitRight = (node) => !!node, shouldVisitRoot = (node) => {
if (includeNull) return this.isRealNodeOrNull(node);
return this.isRealNode(node);
}, shouldProcessRoot = (node) => this.isRealNodeOrNull(node)) {
startNode = this.ensureNode(startNode);
if (!startNode) return [];
const ans = [];
if (iterationType === "RECURSIVE") {
const dfs = (node) => {
if (!shouldVisitRoot(node)) return;
const visitLeft = () => {
if (shouldVisitLeft(node) && (node == null ? void 0 : node.left) !== void 0) dfs(node == null ? void 0 : node.left);
};
const visitRight = () => {
if (shouldVisitRight(node) && (node == null ? void 0 : node.right) !== void 0) dfs(node == null ? void 0 : node.right);
};
switch (pattern) {
case "IN":
visitLeft();
if (shouldProcessRoot(node)) {
ans.push(callback(node));
if (onlyOne) return;
}
visitRight();
break;
case "PRE":
if (shouldProcessRoot(node)) {
ans.push(callback(node));
if (onlyOne) return;
}
visitLeft();
visitRight();
break;
case "POST":
visitLeft();
visitRight();
if (shouldProcessRoot(node)) {
ans.push(callback(node));
if (onlyOne) return;
}
break;
}
};
dfs(startNode);
} else {
const stack = [{ opt: 0 /* VISIT */, node: startNode }];
const pushLeft = (cur) => {
var _a;
if (shouldVisitLeft(cur.node)) stack.push({ opt: 0 /* VISIT */, node: (_a = cur.node) == null ? void 0 : _a.left });
};
const pushRight = (cur) => {
var _a;
if (shouldVisitRight(cur.node)) stack.push({ opt: 0 /* VISIT */, node: (_a = cur.node) == null ? void 0 : _a.right });
};
const pushRoot = (cur) => {
if (shouldVisitRoot(cur.node)) stack.push({ opt: 1 /* PROCESS */, node: cur.node });
};
while (stack.length > 0) {
const cur = stack.pop();
if (cur === void 0) continue;
if (!shouldVisitRoot(cur.node)) continue;
if (cur.opt === 1 /* PROCESS */) {
if (shouldProcessRoot(cur.node) && cur.node !== void 0) {
ans.push(callback(cur.node));
if (onlyOne) return ans;
}
} else {
switch (pattern) {
case "IN":
pushRight(cur);
pushRoot(cur);
pushLeft(cur);
break;
case "PRE":
pushRight(cur);
pushLeft(cur);
pushRoot(cur);
break;
case "POST":
pushRoot(cur);
pushRight(cur);
pushLeft(cur);
break;
}
}
}
}
return ans;
}
/**
* (Protected) Gets the iterator for the tree (default in-order).
* @remarks Time O(N) for full iteration. O(H) to get the first element. Space O(H) for the iterative stack. O(H) for recursive stack.
*
* @param [node=this._root] - The node to start iteration from.
* @returns An iterator for [key, value] pairs.
*/
*_getIterator(node = this._root) {
if (!node) return;
if (this.iterationType === "ITERATIVE") {
const stack = [];
let current = node;
while (current || stack.length > 0) {
while (this.isRealNode(current)) {
stack.push(current);
current = current.left;
}
current = stack.pop();
if (this.isRealNode(current)) {
yield [current.key, current.value];
current = current.right;
}
}
} else {
if (node.left && this.isRealNode(node)) {
yield* this[Symbol.iterator](node.left);
}
yield [node.key, node.value];
if (node.right && this.isRealNode(node)) {
yield* this[Symbol.iterator](node.right);
}
}
}
/**
* (Protected) Snapshots the current tree's configuration options.
* @remarks Time O(1)
*
* @template TK, TV, TR - Generic types for the options.
* @returns The options object.
*/
_snapshotOptions() {
return {
iterationType: this.iterationType,
toEntryFn: this.toEntryFn,
isMapMode: this.isMapMode,
isDuplicate: this.isDuplicate
};
}
/**
* (Protected) Creates a new, empty instance of the same tree constructor.
* @remarks Time O(1)
*
* @template TK, TV, TR - Generic types for the new instance.
* @param [options] - Options for the new tree.
* @returns A new, empty tree.
*/
_createInstance(options) {
const Ctor = this.constructor;
return new Ctor([], { ...this._snapshotOptions(), ...options != null ? options : {} });
}
/**
* (Protected) Creates a new instance of the same tree constructor, potentially with different generic types.
* @remarks Time O(N) (or as per constructor) due to processing the iterable.
*
* @template TK, TV, TR - Generic types for the new instance.
* @param [iter=[]] - An iterable to populate the new tree.
* @param [options] - Options for the new tree.
* @returns A new tree.
*/
_createLike(iter = [], options) {
const Ctor = this.constructor;
return new Ctor(iter, { ...this._snapshotOptions(), ...options != null ? options : {} });
}
/**
* (Protected) Converts a key, node, or entry into a standardized [node, value] tuple.
* @remarks Time O(1)
*
* @param keyNodeOrEntry - The input item.
* @param [value] - An optional value (used if input is just a key).
* @returns A tuple of [node, value].
*/
_keyValueNodeOrEntryToNodeAndValue(keyNodeOrEntry, value) {
if (keyNodeOrEntry === void 0) return [void 0, void 0];
if (keyNodeOrEntry === null) return [null, void 0];
if (this.isNode(keyNodeOrEntry)) return [keyNodeOrEntry, value];
if (this.isEntry(keyNodeOrEntry)) {
const [key, entryValue] = keyNodeOrEntry;
if (key === void 0) return [void 0, void 0];
else if (key === null) return [null, void 0];
const finalValue = value != null ? value : entryValue;
return [this.createNode(key, finalValue), finalValue];
}
return [this.createNode(keyNodeOrEntry, value), value];
}
/**
* (Protected) Helper for cloning. Performs a BFS and sets all nodes to the new tree.
* @remarks Time O(N * M) (O(N) BFS + O(M) `set` for each node).
*
* @param cloned - The new, empty tree instance to populate.
*/
_clone(cloned) {
this.bfs(
(node) => {
if (node === null) cloned.set(null);
else {
cloned.set([node.key, node.value]);
}
},
this._root,
this.iterationType,
true
// Include nulls
);
}
/**
* (Protected) Recursive helper for `toVisual`.
* @remarks Time O(N), Space O(N*H) or O(N^2)
*
* @param node - The current node.
* @param options - Print options.
* @returns Layout information for this subtree.
*/
_displayAux(node, options) {
const emptyDisplayLayout = [["\u2500"], 1, 0, 0];
const newFrame = (n) => ({
node: n,
stage: 0,
leftLayout: emptyDisplayLayout,
rightLayout: emptyDisplayLayout
});
const stack = [newFrame(node)];
let result = emptyDisplayLayout;
const setChildResult = (layout) => {
if (stack.length === 0) {
result = layout;
return;
}
const parent = stack[stack.length - 1];
if (parent.stage === 1) parent.leftLayout = layout;
else parent.rightLayout = layout;
};
while (stack.length > 0) {
const frame = stack[stack.length - 1];
const cur = frame.node;
if (frame.stage === 0) {
if (this._isDisplayLeaf(cur, options)) {
stack.pop();
const layout = this._resolveDisplayLeaf(cur, options, emptyDisplayLayout);
setChildResult(layout);
continue;
}
frame.stage = 1;
stack.push(newFrame(cur.left));
} else if (frame.stage === 1) {
frame.stage = 2;
stack.push(newFrame(cur.right));
} else {
stack.pop();
const line = this.isNIL(cur) ? "S" : String(cur.key);
const layout = _BinaryTree._buildNodeDisplay(line, line.length, frame.leftLayout, frame.rightLayout);
setChildResult(layout);
}
}
return result;
}
static _buildNodeDisplay(line, width, left, right) {
const [leftLines, leftWidth, leftHeight, leftMiddle] = left;
const [rightLines, rightWidth, rightHeight, rightMiddle] = right;
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));
const secondLine = (leftHeight > 0 ? " ".repeat(leftMiddle) + "/" + " ".repeat(leftWidth - leftMiddle - 1) : " ".repeat(leftWidth)) + " ".repeat(width) + (rightHeight > 0 ? " ".repeat(rightMiddle) + "\\" + " ".repeat(rightWidth - rightMiddle - 1) : " ".repeat(rightWidth));
const mergedLines = [firstLine, secondLine];
for (let i = 0; i < Math.max(leftHeight, rightHeight); i++) {
const leftLine = i < leftHeight ? leftLines[i] : " ".repeat(leftWidth);
const rightLine = i < rightHeight ? rightLines[i] : " ".repeat(rightWidth);
mergedLines.push(leftLine + " ".repeat(width) + rightLine);
}
return [
mergedLines,
leftWidth + width + rightWidth,
Math.max(leftHeight, rightHeight) + 2,
leftWidth + Math.floor(width / 2)
];
}
/**
* Check if a node is a display leaf (empty, null, undefined, NIL, or real leaf).
*/
_isDisplayLeaf(node, options) {
const { isShowNull, isShowUndefined, isShowRedBlackNIL } = options;
if (node === null && !isShowNull) return true;
if (node === void 0 && !isShowUndefined) return true;
if (this.isNIL(node) && !isShowRedBlackNIL) return true;
if (node === null || node === void 0) return true;
const hasDisplayableLeft = this._hasDisplayableChild(node.left, options);
const hasDisplayableRight = this._hasDisplayableChild(node.right, options);
return !hasDisplayableLeft && !hasDisplayableRight;
}
_hasDisplayableChild(child, options) {
if (child === null) return !!options.isShowNull;
if (child === void 0) return !!options.isShowUndefined;
if (this.isNIL(child)) return !!options.isShowRedBlackNIL;
return true;
}
/**
* Resolve a display leaf node to its layout.
*/
_resolveDisplayLeaf(node, options, emptyDisplayLayout) {
const { isShowNull, isShowUndefined, isShowRedBlackNIL } = options;
if (node === null && !isShowNull) return emptyDisplayLayout;
if (node === void 0 && !isShowUndefined) return emptyDisplayLayout;
if (this.isNIL(node) && !isShowRedBlackNIL) return emptyDisplayLayout;
if (node !== null && node !== void 0) {
const line2 = this.isNIL(node) ? "S" : String(node.key);
return _BinaryTree._buildNodeDisplay(line2, line2.length, emptyDisplayLayout, emptyDisplayLayout);
}
const line = node === void 0 ? "U" : "N";
return _BinaryTree._buildNodeDisplay(line, line.length, [[""], 1, 0, 0], [[""], 1, 0, 0]);
}
/**
* (Protected) Swaps the key/value properties of two nodes.
* @remarks Time O(1)
*
* @param srcNode - The source node.
* @param destNode - The destination node.
* @returns The `destNode` (now holding `srcNode`'s properties).
*/
_swapProperties(srcNode, destNode) {
srcNode = this.ensureNode(srcNode);
destNode = this.ensureNode(destNode);
if (srcNode && destNode) {
const { key, value } = destNode;
const tempNode = this.createNode(key, value);
if (tempNode) {
destNode.key = srcNode.key;
if (!this._isMapMode) destNode.value = srcNode.value;
srcNode.key = tempNode.key;
if (!this._isMapMode) srcNode.value = tempNode.value;
}
return destNode;
}
return void 0;
}
/**
* (Protected) Replaces a node in the tree with a new node, maintaining children and parent links.
* @remarks Time O(1)
*
* @param oldNode - The node to be replaced.
* @param newNode - The node to insert.
* @returns The `newNode`.
*/
_replaceNode(oldNode, newNode) {
if (oldNode.parent) {
if (oldNode.parent.left === oldNode) {
oldNode.parent.left = newNode;
} else if (oldNode.parent.right === oldNode) {
oldNode.parent.right = newNode;
}
}
newNode.left = oldNode.left;
newNode.right = oldNode.right;
newNode.parent = oldNode.parent;
if (this._root === oldNode) {
this._setRoot(newNode);
}
return newNode;
}
/**
* (Protected) Sets the root node and clears its parent reference.
* @remarks Time O(1)
*
* @param v - The node to set as root.
*/
_setRoot(v) {
if (v) {
v.parent = void 0;
}
this._root = v;
}
_ensurePredicate(keyNodeEntryOrPredicate) {
if (keyNodeEntryOrPredicate === null || keyNodeEntryOrPredicate === void 0)
return (node) => node ? false : false;
if (this._isPredicate(keyNodeEntryOrPredicate)) return keyNodeEntryOrPredicate;
if (this.isRealNode(keyNodeEntryOrPredicate))
return (node) => node === keyNodeEntryOrPredicate;
if (this.isEntry(keyNodeEntryOrPredicate)) {
const [key] = keyNodeEntryOrPredicate;
return (node) => {
if (!node) return false;
return node.key === key;
};
}
return (node) => {
if (!node) return false;
return node.key === keyNodeEntryOrPredicate;
};
}
/**
* (Protected) Checks if an item is a predicate function.
* @remarks Time O(1)
*
* @param p - The item to check.
* @returns True if it's a function.
*/
_isPredicate(p) {
return typeof p === "function";
}
/**
* (Protected) Extracts the key from a key, node, or entry.
* @remarks Time O(1)
*
* @param keyNodeOrEntry - The item.
* @returns The extracted key.
*/
_extractKey(keyNodeOrEntry) {
if (keyNodeOrEntry === null) return null;
if (keyNodeOrEntry === void 0) return;
if (keyNodeOrEntry === this._NIL) return;
if (this.isNode(keyNodeOrEntry)) return keyNodeOrEntry.key;
if (this.isEntry(keyNodeOrEntry)) return keyNodeOrEntry[0];
return keyNodeOrEntry;
}
/**
* (Protected) Sets a value in the external store (Map mode).
* @remarks Time O(1) (average for Map.set).
*
* @param key - The key.
* @param value - The value.
* @returns True if successful.
*/
_setValue(key, value) {
if (key === null || key === void 0) return false;
const node = this._store.get(key);
if (!node) return false;
node.value = value;
return true;
}
/**
* (Protected) Clears all nodes from the tree.
* @remarks Time O(1)
*/
_clearNodes() {
this._setRoot(void 0);
this._size = 0;
}
/**
* (Protected) Clears all values from the external store.
* @remarks Time O(N)
*/
_clearValues() {
this._store.clear();
}
};
// src/data-structures/binary-tree/bst.ts
var BSTNode = class {
/**
* Creates an instance of BSTNode.
* @remarks Time O(1), Space O(1)
*
* @param key - The key of the node.
* @param [value] - The value associated with the key.
*/
constructor(key, value) {
__publicField(this, "key");
__publicField(this, "value");
__publicField(this, "parent");
__publicField(this, "_left");
__publicField(this, "_right");
__publicField(this, "_height", 0);
__publicField(this, "_color", "BLACK");
__publicField(this, "_count", 1);
this.key = key;
this.value = value;
}
/**
* Gets the left child of the node.
* @remarks Time O(1), Space O(1)
*
* @returns The left child.
*/
get left() {
return this._left;
}
/**
* Sets the left child of the node and updates its parent reference.
* @remarks Time O(1), Space O(1)
*
* @param v - The node to set as the left child.
*/
set left(v) {
if (v) v.parent = this;
this._left = v;
}
/**
* Gets the right child of the node.
* @remarks Time O(1), Space O(1)
*
* @returns The right child.
*/
get right() {
return this._right;
}
/**
* Sets the right child of the node and updates its parent reference.
* @remarks Time O(1), Space O(1)
*
* @param v - The node to set as the right child.
*/
set right(v) {
if (v) v.parent = this;
this._right = v;
}
/**
* Gets the height of the node (used in self-balancing trees).
* @remarks Time O(1), Space O(1)
*
* @returns The height.
*/
/* istanbul ignore next -- covered by AVLTree/RedBlackTree tests (subclass uses height) */
get height() {
return this._height;
}
/**
* Sets the height of the node.
* @remarks Time O(1), Space O(1)
*
* @param value - The new height.
*/
/* istanbul ignore next -- covered by AVLTree/RedBlackTree tests (subclass uses height) */
set height(value) {
this._height = value;
}
/**
* Gets the color of the node (used in Red-Black trees).
* @remarks Time O(1), Space O(1)
*
* @returns The node's color.
*/
/* istanbul ignore next -- covered by RedBlackTree tests (subclass uses color) */
get color() {
return this._color;
}
/**
* Sets the color of the node.
* @remarks Time O(1), Space O(1)
*
* @param value - The new color.
*/
/* istanbul ignore next -- covered by RedBlackTree tests (subclass uses color) */
set color(value) {
this._color = value;
}
/**
* Gets the count of nodes in the subtree rooted at this node (used in order-statistic trees).
* @remarks Time O(1), Space O(1)
*
* @returns The subtree node count.
*/
/* istanbul ignore next -- internal field used by subclasses */
get count() {
return this._count;
}
/**
* Sets the count of nodes in the subtree.
* @remarks Time O(1), Space O(1)
*
* @param value - The new count.
*/
/* istanbul ignore next -- internal field used by subclasses */
set count(value) {
this._count = value;
}
/**
* Gets the position of the node relative to its parent.
* @remarks Time O(1), Space O(1)
*
* @returns The family position (e.g., 'ROOT', 'LEFT', 'RIGHT').
*/
get familyPosition() {
if (!this.parent) {
return this.left || this.right ? "ROOT" : "ISOLATED";
}
if (this.parent.left === this) {
return this.left || this.right ? "ROOT_LEFT" : "LEFT";
} else if (this.parent.right === this) {
return this.left || this.right ? "ROOT_RIGHT" : "RIGHT";
}
return "MAL_NODE";
}
};
var BST = class extends BinaryTree {
/**
* Creates an instance of BST.
* @remarks Time O(N log N) or O(N^2) depending on `isBalanceAdd` in `addMany` and input order. Space O(N).
*
* @param [keysNodesEntriesOrRaws=[]] - An iterable of items to set.
* @param [options] - Configuration options for the BST, including comparator.
*/
constructor(keysNodesEntriesOrRaws = [], options) {
super([], options);
__publicField(this, "_root");
__publicField(this, "_enableOrderStatistic", false);
/**
* The comparator function used to determine the order of keys in the tree.
* @remarks Time O(1) Space O(1)
*/
__publicField(this, "_comparator");
if (options) {
if ("comparator" in options && options.comparator !== void 0) {
this._comparator = options.comparator;
} else {
this._comparator = this._createDefaultComparator();
}
if (options.enableOrderStatistic) {
this._enableOrderStatistic = true;
}
} else {
this._comparator = this._createDefaultComparator();
}
if (keysNodesEntriesOrRaws) this.setMany(keysNodesEntriesOrRaws);
}
/**
* Gets the root node of the tree.
* @remarks Time O(1)
*
* @returns The root node.
*/
get root() {
return this._root;
}
/**
* Gets the comparator function used by the tree.
* @remarks Time O(1)
*
* @returns The comparator function.
*/
get comparator() {
return this._comparator;
}
/**
* (Protected) Creates a new BST node.
* @remarks Time O(1), Space O(1)
*
* @param key - The key for the new node.
* @param [value] - The value for the new node (used if not in Map mode).
* @returns The newly created BSTNode.
*/
createNode(key, value) {
return new BSTNode(key, value);
}
/**
* Ensures the input is a node. If it's a key or entry, it searches for the node.
* @remarks Time O(log N) (height of the tree), O(N) worst-case.
*
* @param keyNodeOrEntry - The item to resolve to a node.
* @param [iterationType=this.iterationType] - The traversal method to use if searching.
* @returns The resolved node, or undefined if not found.
*/
ensureNode(keyNodeOrEntry, iterationType = this.iterationType) {
var _a;
return (_a = super.ensureNode(keyNodeOrEntry, iterationType)) != null ? _a : void 0;
}
/**
* Checks if the given item is a `BSTNode` instance.
* @remarks Time O(1), Space O(1)
*
* @param keyNodeOrEntry - The item to check.
* @returns True if it's a BSTNode, false otherwise.
*/
isNode(keyNodeOrEntry) {
return keyNodeOrEntry instanceof BSTNode;
}
/**
* Checks if the given key is valid (comparable).
* @remarks Time O(1)
*
* @param key - The key to validate.
* @returns True if the key is valid, false otherwise.
*/
isValidKey(key) {
return isComparable(key);
}
/**
* Performs a Depth-First Search (DFS) traversal.
* @remarks Time O(N), visits every node. Space O(log N) for the call/explicit stack. O(N) worst-case.
*
* @template C - The type of the callback function.
* @param [callback=this._DEFAULT_NODE_CALLBACK] - Function to call on each node.
* @param [pattern='IN'] - The traversal order ('IN', 'PRE', 'POST').
* @param [onlyOne=false] - If true, stops after the first callback.
* @param [startNode=this._root] - The node to start from.
* @param [iterationType=this.iterationType] - The traversal method.
* @returns An array of callback results.
*/
dfs(callback = this._DEFAULT_NODE_CALLBACK, pattern = "IN", onlyOne = false, startNode = this._root, iterationType = this.iterationType) {
return super.dfs(callback, pattern, onlyOne, startNode, iterationType);
}
/**
* Performs a Breadth-First Search (BFS) or Level-Order traversal.
* @remarks Time O(N), visits every node. Space O(N) in the worst case for the queue.
*
* @template C - The type of the callback function.
* @param [callback=this._DEFAULT_NODE_CALLBACK] - Function to call on each node.
* @param [startNode=this._root] - The node to start from.
* @param [iterationType=this.iterationType] - The traversal method.
* @returns An array of callback results.
*/
bfs(callback = this._DEFAULT_NODE_CALLBACK, startNode = this._root, iterationType = this.iterationType) {
return super.bfs(callback, startNode, iterationType, false);
}
/**
* Returns a 2D array of nodes, grouped by level.
* @remarks Time O(N), visits every node. Space O(N) for the result array and the queue/stack.
*
* @template C - The type of the callback function.
* @param [callback=this._DEFAULT_NODE_CALLBACK] - Function to call on each node.
* @param [startNode=this._root] - The node to start from.
* @param [iterationType=this.iterationType] - The traversal method.
* @returns A 2D array of callback results.
*/
listLevels(callback = this._DEFAULT_NODE_CALLBACK, startNode = this._root, iterationType = this.iterationType) {
return super.listLevels(callback, startNode, iterationType, false);
}
/**
* Gets the first node matching a predicate.
* @remarks Time O(log N) if searching by key, O(N) if searching by predicate. Space O(log N) or O(N).
*
* @param keyNodeEntryOrPredicate - The key, node, entry, or predicate function to search for.
* @param [startNode=this._root] - The node to start the search from.
* @param [iterationType=this.iterationType] - The traversal method.
* @returns The first matching node, or undefined if not found.
* @example
* // Get node object by key
* const bst = new BST<number, string>([[5, 'root'], [3, 'left'], [7, 'right']]);
* const node = bst.getNode(3);
* console.log(node?.key); // 3;
* console.log(node?.value); // 'left';
*/
getNode(keyNodeEntryOrPredicate, startNode = this._root, iterationType = this.iterationType) {
var _a, _b;
if (keyNodeEntryOrPredicate === null || keyNodeEntryOrPredicate === void 0) return void 0;
if (this._isPredicate(keyNodeEntryOrPredicate)) {
return (_a = this.getNodes(keyNodeEntryOrPredicate, true, startNode, iterationType)[0]) != null ? _a : void 0;
}
if (keyNodeEntryOrPredicate instanceof Range) {
return (_b = this.getNodes(
keyNodeEntryOrPredicate,
true,
startNode,
iterationType
)[0]) != null ? _b : void 0;
}
let targetKey;
if (this.isNode(keyNodeEntryOrPredicate)) {
targetKey = keyNodeEntryOrPredicate.key;
} else if (this.isEntry(keyNodeEntryOrPredicate)) {
const k = keyNodeEntryOrPredicate[0];
if (k === null || k === void 0) return void 0;
targetKey = k;
} else {
targetKey = keyNodeEntryOrPredicate;
}
const start = this.ensureNode(startNode);
if (!start) return void 0;
const NIL = this._NIL;
let cur = start;
const cmpFn = this._comparator;
while (cur && cur !== NIL) {
const c = cmpFn(targetKey, cur.key);
if (c === 0) return cur;
cur = c < 0 ? cur._left : cur._right;
}
return void 0;
}
/**
* Searches the tree for nodes matching a predicate, key, or range.
* @remarks This is an optimized search for a BST. If searching by key or range, it prunes branches.
* Time O(H + M) for key/range search (H=height, M=matches). O(N) for predicate search.
* Space O(log N) for the stack.
*
* @template C - The type of the callback function.
* @param keyNodeEntryOrPredicate - The key, node, entry, predicate, or range to search for.
* @param [onlyOne=false] - If true, stops after finding the first match.
* @param [callback=this._DEFAULT_NODE_CALLBACK] - A function to call on matching nodes.
* @param [startNode=this._root] - The node to start the search from.
* @param [iterationType=this.iterationType] - Whether to use 'RECURSIVE' or 'ITERATIVE' search.
* @returns An array of results from the callback function for each matching node.
*/
search(keyNodeEntryOrPredicate, onlyOne = false, callback = this._DEFAULT_NODE_CALLBACK, startNode = this._root, iterationType = this.iterationType) {
if (keyNodeEntryOrPredicate === void 0) return [];
if (keyNodeEntryOrPredicate === null) return [];
startNode = this.ensureNode(startNode);
if (!startNode) return [];
const isRange = this.isRange(keyNodeEntryOrPredicate);
const isPred = !isRange && this._isPredicate(keyNodeEntryOrPredicate);
if (!isRange && !isPred) {
let targetKey;
if (this.isNode(keyNodeEntryOrPredicate)) {
targetKey = keyNodeEntryOrPredicate.key;
} else if (this.isEntry(keyNodeEntryOrPredicate)) {
const k = keyNodeEntryOrPredicate[0];
if (k !== null && k !== void 0) targetKey = k;
} else {
targetKey = keyNodeEntryOrPredicate;
}
if (targetKey === void 0) return [];
const NIL = this._NIL;
const cmpFn = this._comparator;
let cur = startNode;
while (cur && cur !== NIL) {
const c = cmpFn(targetKey, cur.key);
if (c === 0) return [callback(cur)];
cur = c < 0 ? cur._left : cur._right;
}
return [];
}
let predicate;
if (isRange) {
predicate = (node) => {
if (!node) return false;
return keyNodeEntryOrPredicate.isInRange(node.key, this._comparator);
};
} else {
predicate = this._ensurePredicate(keyNodeEntryOrPredicate);
}
const shouldVisitLeft = (cur) => {
if (!cur) return false;
if (!this.isRealNode(cur.left)) return false;
if (isRange) {
const range = keyNodeEntryOrPredicate;
const leftS = range.low;
const leftI = range.includeLow;
return leftI && this._compare(cur.key, leftS) >= 0 || !leftI && this._compare(cur.key, leftS) > 0;
}
if (!isRange && !this._isPredicate(keyNodeEntryOrPredicate)) {
const benchmarkKey = this._extractKey(keyNodeEntryOrPredicate);
return benchmarkKey !== null && benchmarkKey !== void 0 && this._compare(cur.key, benchmarkKey) > 0;
}
return true;
};
const shouldVisitRight = (cur) => {
if (!cur) return false;
if (!this.isRealNode(cur.right)) return false;
if (isRange) {
const range = keyNodeEntryOrPredicate;
const rightS = range.high;
const rightI = range.includeHigh;
return rightI && this._compare(cur.key, rightS) <= 0 || !rightI && this._compare(cur.key, rightS) < 0;
}
if (!isRange && !this._isPredicate(keyNodeEntryOrPredicate)) {
const benchmarkKey = this._extractKey(keyNodeEntryOrPredicate);
return benchmarkKey !== null && benchmarkKey !== void 0 && this._compare(cur.key, benchmarkKey) < 0;
}
return true;
};
return super._dfs(
callback,
"IN",
// In-order is efficient for range/key search
onlyOne,
startNode,
iterationType,
false,
shouldVisitLeft,
shouldVisitRight,
() => true,
// shouldVisitRoot (always visit)
(cur) => !!cur && predicate(cur)
// shouldProcessRoot (only process if predicate matches)
);
}
/**
* Performs an optimized search for nodes within a given key range.
* @remarks Time O(H + M), where H is tree height and M is the number of matches.
*
* @template C - The type of the callback function.
* @param range - A `Range` object or a `[low, high]` tuple.
* @param [callback=this._DEFAULT_NODE_CALLBACK] - A function to call on matching nodes.
* @param [startNode=this._root] - The node to start the search from.
* @param [iterationType=this.iterationType] - The traversal method.
* @returns An array of callback results.
*/
rangeSearch(range, callback = this._DEFAULT_NODE_CALLBACK, startNode = this._root, iterationType = this.iterationType) {
const searchRange = range instanceof Range ? range : new Range(range[0], range[1]);
return this.search(searchRange, false, callback, startNode, iterationType);
}
getByRank(k, callback = this._DEFAULT_NODE_CALLBACK, iterationType = this.iterationType) {
if (!this._enableOrderStatistic) {
raise(Error, ERR.orderStatisticNotEnabled("getByRank"));
}
if (k < 0 || k >= this._size) return void 0;
let actualCallback = void 0;
let actualIterationType = this.iterationType;
if (typeof callback === "string") {
actualIterationType = callback;
} else if (callback) {
actualCallback = callback;
if (iterationType) {
actualIterationType = iterationType;
}
}
const node = actualIterationType === "RECURSIVE" ? this._getByRankRecursive(this._root, k) : this._getByRankIterative(this._root, k);
if (!node) return void 0;
return actualCallback ? actualCallback(node) : node.key;
}
getRank(keyNodeEntryOrPredicate, iterationType = this.iterationType) {
var _a;
if (!this._enableOrderStatistic) {
raise(Error, ERR.orderStatisticNotEnabled("getRank"));
}
if (!this._root || this._size === 0) return -1;
let actualIterationType = this.iterationType;
if (iterationType) actualIterationType = iterationType;
let key;
if (typeof keyNodeEntryOrPredicate === "function") {
const results = this.search(keyNodeEntryOrPredicate, true);
if (results.length === 0 || results[0] === void 0) return -1;
key = results[0];
} else if (keyNodeEntryOrPredicate === null || keyNodeEntryOrPredicate === void 0) {
return -1;
} else if (this.isNode(keyNodeEntryOrPredicate)) {
key = keyNodeEntryOrPredicate.key;
} else if (Array.isArray(keyNodeEntryOrPredicate)) {
key = (_a = keyNodeEntryOrPredicate[0]) != null ? _a : void 0;
if (key === void 0 || key === null) return -1;
} else {
key = keyNodeEntryOrPredicate;
}
if (key === void 0) return -1;
return actualIterationType === "RECURSIVE" ? this._getRankRecursive(this._root, key) : this._getRankIterative(this._root, key);
}
rangeByRank(start, end, callback = this._DEFAULT_NODE_CALLBACK, iterationType = this.iterationType) {
if (!this._enableOrderStatistic) {
raise(Error, ERR.orderStatisticNotEnabled("rangeByRank"));
}
if (this._size === 0) return [];
const lo = Math.max(0, start);
const hi = Math.min(this._size - 1, end);
if (lo > hi) return [];
let actualCallback = void 0;
let actualIterationType = this.iterationType;
if (typeof callback === "string") {
actualIterationType = callback;
} else if (callback) {
actualCallback = callback;
if (iterationType) {
actualIterationType = iterationType;
}
}
const results = [];
const count = hi - lo + 1;
const startNode = actualIterationType === "RECURSIVE" ? this._getByRankRecursive(this._root, lo) : this._getByRankIterative(this._root, lo);
if (!startNode) return [];
let collected = 0;
const cb = actualCallback != null ? actualCallback : this._DEFAULT_NODE_CALLBACK;
let current = startNode;
while (current && collected < count) {
results.push(cb(current));
collected++;
if (collected < count) {
current = this._next(current);
}
}
return results;
}
/**
* Adds a new node to the BST based on key comparison.
* @remarks Time O(log N), where H is tree height. O(N) worst-case (unbalanced tree), O(log N) average. Space O(1).
*
* @param keyNodeOrEntry - The key, node, or entry to set.
* @param [value] - The value, if providing just a key.
* @returns True if the addition was successful, false otherwise.
* @example
* // Set a key-value pair
* const bst = new BST<number, string>();
* bst.set(1, 'one');
* bst.set(2, 'two');
* console.log(bst.get(1)); // 'one';
*/
set(keyNodeOrEntry, value) {
const [newNode] = this._keyValueNodeOrEntryToNodeAndValue(keyNodeOrEntry, value);
if (newNode === void 0) return false;
if (this._root === void 0) {
this._setRoot(newNode);
if (this._isMapMode && this.isRealNode(newNode)) this._store.set(newNode.key, newNode);
this._size++;
this._updateCount(newNode);
return true;
}
let current = this._root;
while (current !== void 0) {
if (this._compare(current.key, newNode.key) === 0) {
this._replaceNode(current, newNode);
if (this._isMapMode && this.isRealNode(newNode)) this._store.set(current.key, newNode);
return true;
} else if (this._compare(current.key, newNode.key) > 0) {
if (current.left === void 0) {
current.left = newNode;
if (this._isMapMode && this.isRealNode(newNode)) this._store.set(newNode.key, newNode);
this._size++;
this._updateCountAlongPath(newNode);
return true;
}
if (current.left !== null) current = current.left;
} else {
if (current.right === void 0) {
current.right = newNode;
if (this._isMapMode && this.isRealNode(newNode)) this._store.set(newNode.key, newNode);
this._size++;
this._updateCountAlongPath(newNode);
return true;
}
if (current.right !== null) current = current.right;
}
}
return false;
}
/**
* Adds multiple items to the tree.
* @remarks If `isBalanceAdd` is true, sorts the input and builds a balanced tree. Time O(N log N) (due to sort and balanced set).
* If false, adds items one by one. Time O(N * H), which is O(N^2) worst-case.
* Space O(N) for sorting and recursion/iteration stack.
*
* @param keysNodesEntriesOrRaws - An iterable of items to set.
* @param [values] - An optional parallel iterable of values.
* @param [isBalanceAdd=true] - If true, builds a balanced tree from the items.
* @param [iterationType=this.iterationType] - The traversal method for balanced set (recursive or iterative).
* @returns An array of booleans indicating the success of each individual `set` operation.
* @example
* // Set multiple key-value pairs
* const bst = new BST<number, string>();
* bst.setMany([[1, 'a'], [2, 'b'], [3, 'c']]);
* console.log(bst.size); // 3;
* console.log(bst.get(2)); // 'b';
*/
setMany(keysNodesEntriesOrRaws, values, isBalanceAdd = true, iterationType = this.iterationType) {
const inserted = [];
const valuesIterator = values == null ? void 0 : values[Symbol.iterator]();
if (!isBalanceAdd) {
for (let kve of keysNodesEntriesOrRaws) {
const val = valuesIterator == null ? void 0 : valuesIterator.next().value;
if (this.isRaw(kve)) kve = this._toEntryFn(kve);
inserted.push(this.set(kve, val));
}
return inserted;
}
const realBTNExemplars = [];
let i = 0;
for (const kve of keysNodesEntriesOrRaws) {
realBTNExemplars.push({ key: kve, value: valuesIterator == null ? void 0 : valuesIterator.next().value, orgIndex: i++ });
}
const sorted = realBTNExemplars.sort(({ key: a }, { key: b }) => {
let keyA, keyB;
if (this.isRaw(a)) keyA = this._toEntryFn(a)[0];
else if (this.isEntry(a)) keyA = a[0];
else if (this.isRealNode(a)) keyA = a.key;
else keyA = a;
if (this.isRaw(b)) keyB = this._toEntryFn(b)[0];
else if (this.isEntry(b)) keyB = b[0];
else if (this.isRealNode(b)) keyB = b.key;
else keyB = b;
if (keyA != null && keyB != null) return this._compare(keyA, keyB);
return 0;
});
const _dfs = (arr) => {
if (arr.length === 0) return;
const mid = Math.floor((arr.length - 1) / 2);
const { key, value, orgIndex } = arr[mid];
if (this.isRaw(key)) {
const entry = this._toEntryFn(key);
inserted[orgIndex] = this.set(entry);
} else {
inserted[orgIndex] = this.set(key, value);
}
_dfs(arr.slice(0, mid));
_dfs(arr.slice(mid + 1));
};
const _iterate = () => {
const n = sorted.length;
const stack = [[0, n - 1]];
while (stack.length > 0) {
const popped = stack.pop();
if (!popped) continue;
const [l, r] = popped;
if (l > r) continue;
const m = l + Math.floor((r - l) / 2);
const { key, value, orgIndex } = sorted[m];
if (this.isRaw(key)) {
const entry = this._toEntryFn(key);
inserted[orgIndex] = this.set(entry);
} else {
inserted[orgIndex] = this.set(key, value);
}
stack.push([m + 1, r]);
stack.push([l, m - 1]);
}
};
if (iterationType === "RECURSIVE") _dfs(sorted);
else _iterate();
return inserted;
}
ceiling(keyNodeEntryOrPredicate, callback = this._DEFAULT_NODE_CALLBACK, iterationType) {
let actualCallback = void 0;
let actualIterationType = this.iterationType;
if (typeof callback === "string") {
actualIterationType = callback;
} else if (callback) {
actualCallback = callback;
if (iterationType) {
actualIterationType = iterationType;
}
}
const node = this._bound(keyNodeEntryOrPredicate, true, actualIterationType);
if (!actualCallback) {
return node == null ? void 0 : node.key;
}
return node ? actualCallback(node) : void 0;
}
higher(keyNodeEntryOrPredicate, callback = this._DEFAULT_NODE_CALLBACK, iterationType) {
let actualCallback = void 0;
let actualIterationType = this.iterationType;
if (typeof callback === "string") {
actualIterationType = callback;
} else if (callback) {
actualCallback = callback;
if (iterationType) {
actualIterationType = iterationType;
}
}
const node = this._bound(keyNodeEntryOrPredicate, false, actualIterationType);
if (!actualCallback) {
return node == null ? void 0 : node.key;
}
return node ? actualCallback(node) : void 0;
}
floor(keyNodeEntryOrPredicate, callback = this._DEFAULT_NODE_CALLBACK, iterationType) {
if (keyNodeEntryOrPredicate === null || keyNodeEntryOrPredicate === void 0) {
if (typeof callback === "string" || !callback) {
return void 0;
}
return void 0;
}
let actualCallback = void 0;
let actualIterationType = this.iterationType;
if (typeof callback === "string") {
actualIterationType = callback;
} else if (callback) {
actualCallback = callback;
if (iterationType) {
actualIterationType = iterationType;
}
}
if (this._isPredicate(keyNodeEntryOrPredicate)) {
const node = this._floorByPredicate(keyNodeEntryOrPredicate, actualIterationType);
if (!actualCallback) {
return node == null ? void 0 : node.key;
}
return node ? actualCallback(node) : void 0;
}
let targetKey;
if (this.isNode(keyNodeEntryOrPredicate)) {
targetKey = keyNodeEntryOrPredicate.key;
} else if (this.isEntry(keyNodeEntryOrPredicate)) {
const key = keyNodeEntryOrPredicate[0];
if (key === null || key === void 0) {
if (typeof callback === "string" || !callback) {
return void 0;
}
return void 0;
}
targetKey = key;
} else {
targetKey = keyNodeEntryOrPredicate;
}
if (targetKey !== void 0) {
const node = this._floorByKey(targetKey, actualIterationType);
if (!actualCallback) {
return node == null ? void 0 : node.key;
}
return node ? actualCallback(node) : void 0;
}
if (typeof callback === "string" || !callback) {
return void 0;
}
return void 0;
}
lower(keyNodeEntryOrPredicate, callback, iterationType) {
if (keyNodeEntryOrPredicate === null || keyNodeEntryOrPredicate === void 0) {
if (typeof callback === "string" || !callback) {
return void 0;
}
return void 0;
}
let actualCallback = void 0;
let actualIterationType = this.iterationType;
if (typeof callback === "string") {
actualIterationType = callback;
} else if (callback) {
actualCallback = callback;
if (iterationType) {
actualIterationType = iterationType;
}
}
if (this._isPredicate(keyNodeEntryOrPredicate)) {
const node = this._lowerByPredicate(keyNodeEntryOrPredicate, actualIterationType);
if (!actualCallback) {
return node == null ? void 0 : node.key;
}
return node ? actualCallback(node) : void 0;
}
let targetKey;
if (this.isNode(keyNodeEntryOrPredicate)) {
targetKey = keyNodeEntryOrPredicate.key;
} else if (this.isEntry(keyNodeEntryOrPredicate)) {
const key = keyNodeEntryOrPredicate[0];
if (key === null || key === void 0) {
if (typeof callback === "string" || !callback) {
return void 0;
}
return void 0;
}
targetKey = key;
} else {
targetKey = keyNodeEntryOrPredicate;
}
if (targetKey !== void 0) {
const node = this._lowerByKey(targetKey, actualIterationType);
if (!actualCallback) {
return node == null ? void 0 : node.key;
}
return node ? actualCallback(node) : void 0;
}
if (typeof callback === "string" || !callback) {
return void 0;
}
return void 0;
}
/**
* Traverses the tree and returns nodes that are lesser or greater than a target node.
* @remarks Time O(N), as it performs a full traversal. Space O(log N) or O(N).
*
* @template C - The type of the callback function.
* @param [callback=this._DEFAULT_NODE_CALLBACK] - Function to call on matching nodes.
* @param [lesserOrGreater=-1] - -1 for lesser, 1 for greater, 0 for equal.
* @param [targetNode=this._root] - The node to compare against.
* @param [iterationType=this.iterationType] - The traversal method.
* @returns An array of callback results.
*/
lesserOrGreaterTraverse(callback = this._DEFAULT_NODE_CALLBACK, lesserOrGreater = -1, targetNode = this._root, iterationType = this.iterationType) {
const targetNodeEnsured = this.ensureNode(targetNode);
const ans = [];
if (!this._root || !targetNodeEnsured) return ans;
const targetKey = targetNodeEnsured.key;
if (iterationType === "RECURSIVE") {
const dfs = (cur) => {
const compared = this._compare(cur.key, targetKey);
if (Math.sign(compared) == lesserOrGreater) ans.push(callback(cur));
if (this.isRealNode(cur.left)) dfs(cur.left);
if (this.isRealNode(cur.right)) dfs(cur.right);
};
dfs(this._root);
return ans;
} else {
const queue = new Queue([this._root]);
while (queue.length > 0) {
const cur = queue.shift();
if (this.isRealNode(cur)) {
const compared = this._compare(cur.key, targetKey);
if (Math.sign(compared) == lesserOrGreater) ans.push(callback(cur));
if (this.isRealNode(cur.left)) queue.push(cur.left);
if (this.isRealNode(cur.right)) queue.push(cur.right);
}
}
return ans;
}
}
/**
* Rebuilds the tree to be perfectly balanced.
* @remarks Time O(N) (O(N) for DFS, O(N) for sorted build). Space O(N) for node array and recursion stack.
*
* @param [iterationType=this.iterationType] - The traversal method for the initial node export.
* @returns True if successful, false if the tree was empty.
* @example
* // Rebalance the tree
* const bst = new BST<number>();
* // Insert in sorted order (worst case for BST)
* for (let i = 1; i <= 7; i++) bst.add(i);
* console.log(bst.isAVLBalanced()); // false;
* bst.perfectlyBalance();
* console.log(bst.isAVLBalanced()); // true;
*/
perfectlyBalance(iterationType = this.iterationType) {
const nodes = this.dfs((node) => node, "IN", false, this._root, iterationType);
const n = nodes.length;
this._clearNodes();
if (n === 0) return false;
const build = (l, r, parent) => {
if (l > r) return void 0;
const m = l + (r - l >> 1);
const root = nodes[m];
const leftChild = build(l, m - 1, root);
const rightChild = build(m + 1, r, root);
root.left = leftChild;
root.right = rightChild;
root.parent = parent;
return root;
};
const newRoot = build(0, n - 1, void 0);
this._setRoot(newRoot);
this._size = n;
return true;
}
/**
* Checks if the tree meets the AVL balance condition (height difference <= 1).
* @remarks Time O(N), as it must visit every node to compute height. Space O(log N) for recursion or O(N) for iterative map.
*
* @param [iterationType=this.iterationType] - The traversal method.
* @returns True if the tree is AVL balanced, false otherwise.
* @example
* // Check if tree is height-balanced
* const bst = new BST<number>([3, 1, 5, 2, 4]);
* console.log(bst.isAVLBalanced()); // true;
*/
isAVLBalanced(iterationType = this.iterationType) {
if (!this._root) return true;
let balanced = true;
if (iterationType === "RECURSIVE") {
const _height = (cur) => {
if (!cur) return 0;
const leftHeight = _height(cur.left);
const rightHeight = _height(cur.right);
if (Math.abs(leftHeight - rightHeight) > 1) balanced = false;
return Math.max(leftHeight, rightHeight) + 1;
};
_height(this._root);
} else {
const stack = [];
let node = this._root, last = void 0;
const depths = /* @__PURE__ */ new Map();
while (stack.length > 0 || node) {
if (node) {
stack.push(node);
if (node.left !== null) node = node.left;
} else {
node = stack[stack.length - 1];
if (!node.right || last === node.right) {
node = stack.pop();
if (node) {
const left = node.left ? depths.get(node.left) : -1;
const right = node.right ? depths.get(node.right) : -1;
if (Math.abs(left - right) > 1) return false;
depths.set(node, 1 + Math.max(left, right));
last = node;
node = void 0;
}
} else node = node.right;
}
}
}
return balanced;
}
/**
* Creates a new BST by mapping each [key, value] pair to a new entry.
* @remarks Time O(N * H), where N is nodes in this tree, and H is height of the new tree during insertion.
* Space O(N) for the new tree.
*
* @template MK - New key type.
* @template MV - New value type.
* @template MR - New raw type.
* @param callback - A function to map each [key, value] pair.
* @param [options] - Options for the new BST.
* @param [thisArg] - `this` context for the callback.
* @returns A new, mapped BST.
* @example
* // Transform to new tree
* const bst = new BST<number, number>([[1, 10], [2, 20], [3, 30]]);
* const doubled = bst.map((value, key) => [key, (value ?? 0) * 2] as [number, number]);
* console.log([...doubled.values()]); // [20, 40, 60];
*/
map(callback, options, thisArg) {
const out = this._createLike([], options);
let index = 0;
for (const [key, value] of this) {
out.set(callback.call(thisArg, value, key, index++, this));
}
return out;
}
/**
* Deletes nodes that match a key, node, entry, predicate, or range.
*
* @remarks
* Time Complexity: O(N) for search + O(M log N) for M deletions, where N is tree size.
* Space Complexity: O(M) for storing matched nodes and result map.
*
* @template K - The key type.
* @template V - The value type.
*
* @param keyNodeEntryOrPredicate - The search criteria. Can be one of:
* - A key (type K): searches for exact key match using the comparator.
* - A BSTNode: searches for the matching node in the tree.
* - An entry tuple: searches for the key-value pair.
* - A NodePredicate function: tests each node and returns true for matches.
* - A Range object: searches for nodes whose keys fall within the specified range (inclusive/exclusive based on range settings).
* - null or undefined: treated as no match, returns empty results.
*
* @param onlyOne - If true, stops the search after finding the first match and only deletes that one node.
* If false (default), searches for and deletes all matching nodes.
*
* @param startNode - The node to start the search from. Can be:
* - A key, node, or entry: the method resolves it to a node and searches from that subtree.
* - null or undefined: defaults to the root, searching the entire tree.
* - Default value: this._root (the tree's root).
*
* @param iterationType - Controls the internal traversal implementation:
* - 'RECURSIVE': uses recursive function calls for traversal.
* - 'ITERATIVE': uses explicit stack-based iteration.
* - Default: this.iterationType (the tree's default iteration mode).
*
* @returns A Map<K, boolean> containing the deletion results:
* - Key: the matched node's key.
* - Value: true if the deletion succeeded, false if it failed (e.g., key not found during deletion phase).
* - If no nodes match the search criteria, the returned map is empty.
*/
deleteWhere(keyNodeEntryOrPredicate, onlyOne = false, startNode = this._root, iterationType = this.iterationType) {
const toDelete = this.search(keyNodeEntryOrPredicate, onlyOne, (node) => node, startNode, iterationType);
let deleted = false;
for (const node of toDelete) {
if (this.delete(node)) deleted = true;
}
return deleted;
}
/**
* (Protected) Creates the default comparator function for keys that don't have a custom comparator.
* @remarks Time O(1) Space O(1)
* @returns The default comparator function.
*/
_createDefaultComparator() {
return (a, b) => {
if (isComparable(a) && isComparable(b)) {
if (a > b) return 1;
if (a < b) return -1;
return 0;
}
if (a instanceof Date && b instanceof Date) {
const ta = a.getTime();
const tb = b.getTime();
if (Number.isNaN(ta) || Number.isNaN(tb)) raise(TypeError, ERR.invalidDate("BST"));
return ta > tb ? 1 : ta < tb ? -1 : 0;
}
if (typeof a === "object" || typeof b === "object") {
raise(TypeError, ERR.comparatorRequired("BST"));
}
return 0;
};
}
/**
* (Protected) Binary search for floor by key with pruning optimization.
* Performs standard BST binary search, choosing left or right subtree based on comparator result.
* Finds first node where key <= target.
* @remarks Time O(h) where h is tree height.
*
* @param key - The target key to search for.
* @param iterationType - The iteration type (RECURSIVE or ITERATIVE).
* @returns The first node with key <= target, or undefined if none exists.
*/
_floorByKey(key, iterationType) {
var _a, _b;
if (iterationType === "RECURSIVE") {
const dfs = (cur) => {
if (!this.isRealNode(cur)) return void 0;
const cmp = this.comparator(cur.key, key);
if (cmp <= 0) {
const rightResult = dfs(cur.right);
return rightResult != null ? rightResult : cur;
} else {
return dfs(cur.left);
}
};
return dfs(this.root);
} else {
let current = this.root;
let result = void 0;
while (this.isRealNode(current)) {
const cmp = this.comparator(current.key, key);
if (cmp <= 0) {
result = current;
current = (_a = current.right) != null ? _a : void 0;
} else {
current = (_b = current.left) != null ? _b : void 0;
}
}
return result;
}
}
/**
* (Protected) In-order traversal search for floor by predicate.
* Falls back to linear in-order traversal when predicate-based search is required.
* Returns the last node that satisfies the predicate function.
* @remarks Time Complexity: O(n) since it may visit every node.
* Space Complexity: O(h) for recursion, O(h) for iterative stack.
*
* @param predicate - The predicate function to test nodes.
* @param iterationType - The iteration type (RECURSIVE or ITERATIVE).
* @returns The last node satisfying predicate (highest key), or undefined if none found.
*/
_floorByPredicate(predicate, iterationType) {
if (iterationType === "RECURSIVE") {
let result = void 0;
const dfs = (cur) => {
if (!this.isRealNode(cur)) return;
if (this.isRealNode(cur.left)) dfs(cur.left);
if (predicate(cur)) {
result = cur;
}
if (this.isRealNode(cur.right)) dfs(cur.right);
};
dfs(this.root);
return result;
} else {
const stack = [];
let current = this.root;
let result = void 0;
while (stack.length > 0 || this.isRealNode(current)) {
if (this.isRealNode(current)) {
stack.push(current);
current = current.left;
} else {
const node = stack.pop();
if (!this.isRealNode(node)) break;
if (predicate(node)) {
result = node;
}
current = node.right;
}
}
return result;
}
}
/**
* (Protected) Binary search for lower by key with pruning optimization.
* Performs standard BST binary search, choosing left or right subtree based on comparator result.
* Finds first node where key < target.
* @remarks Time O(h) where h is tree height.
*
* @param key - The target key to search for.
* @param iterationType - The iteration type (RECURSIVE or ITERATIVE).
* @returns The first node with key < target, or undefined if none exists.
*/
_lowerByKey(key, iterationType) {
var _a, _b;
if (iterationType === "RECURSIVE") {
const dfs = (cur) => {
if (!this.isRealNode(cur)) return void 0;
const cmp = this.comparator(cur.key, key);
if (cmp < 0) {
const rightResult = dfs(cur.right);
return rightResult != null ? rightResult : cur;
} else {
return dfs(cur.left);
}
};
return dfs(this.root);
} else {
let current = this.root;
let result = void 0;
while (this.isRealNode(current)) {
const cmp = this.comparator(current.key, key);
if (cmp < 0) {
result = current;
current = (_a = current.right) != null ? _a : void 0;
} else {
current = (_b = current.left) != null ? _b : void 0;
}
}
return result;
}
}
/**
* (Protected) In-order traversal search for lower by predicate.
* Falls back to linear in-order traversal when predicate-based search is required.
* Returns the node that satisfies the predicate and appears last in in-order traversal.
* @remarks Time Complexity: O(n) since it may visit every node.
* Space Complexity: O(h) for recursion, O(h) for iterative stack.
*
* @param predicate - The predicate function to test nodes.
* @param iterationType - The iteration type (RECURSIVE or ITERATIVE).
* @returns The last node satisfying predicate (highest key < target), or undefined if none found.
*/
_lowerByPredicate(predicate, iterationType) {
if (iterationType === "RECURSIVE") {
let result = void 0;
const dfs = (cur) => {
if (!this.isRealNode(cur)) return;
if (this.isRealNode(cur.left)) dfs(cur.left);
if (predicate(cur)) {
result = cur;
}
if (this.isRealNode(cur.right)) dfs(cur.right);
};
dfs(this.root);
return result;
} else {
const stack = [];
let current = this.root;
let result = void 0;
while (stack.length > 0 || this.isRealNode(current)) {
if (this.isRealNode(current)) {
stack.push(current);
current = current.left;
} else {
const node = stack.pop();
if (!this.isRealNode(node)) break;
if (predicate(node)) {
result = node;
}
current = node.right;
}
}
return result;
}
}
/**
* (Protected) Core bound search implementation supporting all parameter types.
* Unified logic for both lowerBound and upperBound.
* Resolves various input types (Key, Node, Entry, Predicate) using parent class utilities.
* @param keyNodeEntryOrPredicate - The key, node, entry, or predicate function to search for.
* @param isLower - True for lowerBound (>=), false for upperBound (>).
* @param iterationType - The iteration type (RECURSIVE or ITERATIVE).
* @returns The first matching node, or undefined if no such node exists.
*/
_bound(keyNodeEntryOrPredicate, isLower, iterationType) {
if (keyNodeEntryOrPredicate === null || keyNodeEntryOrPredicate === void 0) {
return void 0;
}
if (this._isPredicate(keyNodeEntryOrPredicate)) {
return this._boundByPredicate(keyNodeEntryOrPredicate, iterationType);
}
let targetKey;
if (this.isNode(keyNodeEntryOrPredicate)) {
targetKey = keyNodeEntryOrPredicate.key;
} else if (this.isEntry(keyNodeEntryOrPredicate)) {
const key = keyNodeEntryOrPredicate[0];
if (key === null || key === void 0) {
return void 0;
}
targetKey = key;
} else {
targetKey = keyNodeEntryOrPredicate;
}
if (targetKey !== void 0) {
return this._boundByKey(targetKey, isLower, iterationType);
}
return void 0;
}
/**
* (Protected) Binary search for bound by key with pruning optimization.
* Performs standard BST binary search, choosing left or right subtree based on comparator result.
* For lowerBound: finds first node where key >= target.
* For upperBound: finds first node where key > target.
* @param key - The target key to search for.
* @param isLower - True for lowerBound (>=), false for upperBound (>).
* @param iterationType - The iteration type (RECURSIVE or ITERATIVE).
* @returns The first node matching the bound condition, or undefined if none exists.
*/
_boundByKey(key, isLower, iterationType) {
var _a, _b;
if (iterationType === "RECURSIVE") {
const dfs = (cur) => {
if (!this.isRealNode(cur)) return void 0;
const cmp = this.comparator(cur.key, key);
const condition = isLower ? cmp >= 0 : cmp > 0;
if (condition) {
const leftResult = dfs(cur.left);
return leftResult != null ? leftResult : cur;
} else {
return dfs(cur.right);
}
};
return dfs(this.root);
} else {
let current = this.root;
let result = void 0;
while (this.isRealNode(current)) {
const cmp = this.comparator(current.key, key);
const condition = isLower ? cmp >= 0 : cmp > 0;
if (condition) {
result = current;
current = (_a = current.left) != null ? _a : void 0;
} else {
current = (_b = current.right) != null ? _b : void 0;
}
}
return result;
}
}
/**
* (Protected) In-order traversal search by predicate.
* Falls back to linear in-order traversal when predicate-based search is required.
* Returns the first node that satisfies the predicate function.
* Note: Predicate-based search cannot leverage BST's binary search optimization.
* Time Complexity: O(n) since it may visit every node.
* @param predicate - The predicate function to test nodes.
* @param iterationType - The iteration type (RECURSIVE or ITERATIVE).
* @returns The first node satisfying predicate, or undefined if none found.
*/
_boundByPredicate(predicate, iterationType) {
if (iterationType === "RECURSIVE") {
let result = void 0;
const dfs = (cur) => {
if (result || !this.isRealNode(cur)) return;
if (this.isRealNode(cur.left)) dfs(cur.left);
if (!result && predicate(cur)) {
result = cur;
}
if (!result && this.isRealNode(cur.right)) dfs(cur.right);
};
dfs(this.root);
return result;
} else {
const stack = [];
let current = this.root;
while (stack.length > 0 || this.isRealNode(current)) {
if (this.isRealNode(current)) {
stack.push(current);
current = current.left;
} else {
const node = stack.pop();
if (!this.isRealNode(node)) break;
if (predicate(node)) {
return node;
}
current = node.right;
}
}
return void 0;
}
}
/**
* (Protected) Creates a new, empty instance of the same BST constructor.
* @remarks Time O(1)
*
* @template TK, TV, TR - Generic types for the new instance.
* @param [options] - Options for the new BST.
* @returns A new, empty BST.
*/
_createInstance(options) {
const Ctor = this.constructor;
return new Ctor([], { ...this._snapshotOptions(), ...options != null ? options : {} });
}
/**
* (Protected) Creates a new instance of the same BST constructor, potentially with different generic types.
* @remarks Time O(N log N) or O(N^2) (from constructor) due to processing the iterable.
*
* @template TK, TV, TR - Generic types for the new instance.
* @param [iter=[]] - An iterable to populate the new BST.
* @param [options] - Options for the new BST.
* @returns A new BST.
*/
_createLike(iter = [], options) {
const Ctor = this.constructor;
return new Ctor(iter, { ...this._snapshotOptions(), ...options != null ? options : {} });
}
/**
* (Protected) Snapshots the current BST's configuration options.
* @remarks Time O(1)
*
* @template TK, TV, TR - Generic types for the options.
* @returns The options object.
*/
_snapshotOptions() {
return {
...super._snapshotOptions(),
comparator: this._comparator,
enableOrderStatistic: this._enableOrderStatistic
};
}
/**
* (Protected) Converts a key, node, or entry into a standardized [node, value] tuple.
* @remarks Time O(1)
*
* @param keyNodeOrEntry - The input item.
* @param [value] - An optional value (used if input is just a key).
* @returns A tuple of [node, value].
*/
_keyValueNodeOrEntryToNodeAndValue(keyNodeOrEntry, value) {
const [node, entryValue] = super._keyValueNodeOrEntryToNodeAndValue(keyNodeOrEntry, value);
if (node === null) return [void 0, void 0];
return [node, value != null ? value : entryValue];
}
/**
* (Protected) Sets the root node and clears its parent reference.
* @remarks Time O(1)
*
* @param v - The node to set as root.
*/
/**
* (Protected) Recalculates the subtree count for a single node.
* @remarks Time O(1). Only active when enableOrderStatistic is true.
*/
_updateCount(node) {
if (!this._enableOrderStatistic) return;
node._count = 1 + (this.isRealNode(node.left) ? node.left._count : 0) + (this.isRealNode(node.right) ? node.right._count : 0);
}
/**
* (Protected) Updates subtree counts from a node up to the root.
* @remarks Time O(log n). Only active when enableOrderStatistic is true.
*/
_updateCountAlongPath(node) {
if (!this._enableOrderStatistic) return;
let current = node;
while (current) {
this._updateCount(current);
current = current.parent;
}
}
/**
* (Protected) Finds the node at position k in tree order (iterative).
* @remarks Time O(log n), Space O(1)
*/
_getByRankIterative(node, k) {
let current = node;
let remaining = k;
while (current) {
const leftCount = this.isRealNode(current.left) ? current.left._count : 0;
if (remaining < leftCount) {
current = current.left;
} else if (remaining === leftCount) {
return current;
} else {
remaining = remaining - leftCount - 1;
current = current.right;
}
}
return void 0;
}
/**
* (Protected) Finds the node at position k in tree order (recursive).
* @remarks Time O(log n), Space O(log n) call stack
*/
_getByRankRecursive(node, k) {
if (!node) return void 0;
const leftCount = this.isRealNode(node.left) ? node.left._count : 0;
if (k < leftCount) return this._getByRankRecursive(node.left, k);
if (k === leftCount) return node;
return this._getByRankRecursive(node.right, k - leftCount - 1);
}
/**
* (Protected) Computes the rank of a key iteratively.
* @remarks Time O(log n), Space O(1)
*/
_getRankIterative(node, key) {
let rank = 0;
let current = node;
while (this.isRealNode(current)) {
const cmp = this._compare(current.key, key);
if (cmp > 0) {
current = current.left;
} else if (cmp < 0) {
rank += (this.isRealNode(current.left) ? current.left._count : 0) + 1;
current = current.right;
} else {
rank += this.isRealNode(current.left) ? current.left._count : 0;
return rank;
}
}
return rank;
}
/**
* (Protected) Computes the rank of a key recursively.
* @remarks Time O(log n), Space O(log n) call stack
*/
_getRankRecursive(node, key) {
if (!node) return 0;
const cmp = this._compare(node.key, key);
if (cmp > 0) {
return this._getRankRecursive(node.left, key);
} else if (cmp < 0) {
return (this.isRealNode(node.left) ? node.left._count : 0) + 1 + this._getRankRecursive(node.right, key);
} else {
return this.isRealNode(node.left) ? node.left._count : 0;
}
}
/**
* (Protected) Finds the in-order successor of a node.
* @remarks Time O(log n), Space O(1)
*/
_next(node) {
if (this.isRealNode(node.right)) {
let current2 = node.right;
while (this.isRealNode(current2.left)) {
current2 = current2.left;
}
return current2;
}
let current = node;
let parent = current.parent;
while (parent && current === parent.right) {
current = parent;
parent = parent.parent;
}
return parent;
}
_setRoot(v) {
if (v) v.parent = void 0;
this._root = v;
}
/**
* (Protected) Compares two keys using the tree's comparator and reverse setting.
* @remarks Time O(1) Space O(1)
*
* @param a - The first key.
* @param b - The second key.
* @returns A number (1, -1, or 0) representing the comparison.
*/
_compare(a, b) {
return this._comparator(a, b);
}
/**
* (Private) Deletes a node by its key.
* @remarks Standard BST deletion algorithm. Time O(log N), O(N) worst-case. Space O(1).
*
* @param key - The key of the node to delete.
* @returns True if the node was found and deleted, false otherwise.
*/
_deleteByKey(key) {
let node = this._root;
while (node) {
const cmp = this._compare(node.key, key);
if (cmp === 0) break;
node = cmp > 0 ? node.left : node.right;
}
if (!node) return false;
const transplant = (u, v) => {
const p = u == null ? void 0 : u.parent;
if (!p) {
this._setRoot(v);
} else if (p.left === u) {
p.left = v;
} else {
p.right = v;
}
if (v) v.parent = p;
};
const minNode = (x) => {
if (!x) return void 0;
while (x.left !== void 0 && x.left !== null) x = x.left;
return x;
};
let countUpdateStart;
if (node.left === void 0) {
countUpdateStart = node.parent;
transplant(node, node.right);
} else if (node.right === void 0) {
countUpdateStart = node.parent;
transplant(node, node.left);
} else {
const succ = minNode(node.right);
if (succ.parent !== node) {
countUpdateStart = succ.parent;
transplant(succ, succ.right);
succ.right = node.right;
if (succ.right) succ.right.parent = succ;
} else {
countUpdateStart = succ;
}
transplant(node, succ);
succ.left = node.left;
if (succ.left) succ.left.parent = succ;
}
this._updateCountAlongPath(countUpdateStart);
this._size = Math.max(0, this._size - 1);
return true;
}
};
// src/data-structures/binary-tree/red-black-tree.ts
var RedBlackTreeNode = class {
/**
* Create a Red-Black Tree node.
* @remarks Time O(1), Space O(1)
* @param key - Node key.
* @param [value] - Node value (unused in map mode trees).
* @param color - Node color.
*/
constructor(key, value, color = "BLACK") {
__publicField(this, "key");
__publicField(this, "value");
__publicField(this, "parent");
__publicField(this, "_left");
__publicField(this, "_right");
__publicField(this, "_height", 0);
__publicField(this, "_color", "BLACK");
__publicField(this, "_count", 1);
this.key = key;
this.value = value;
this.color = color;
}
/**
* Get the left child pointer.
* @remarks Time O(1), Space O(1)
* @returns Left child node, or null/undefined.
*/
get left() {
return this._left;
}
/**
* Set the left child and update its parent pointer.
* @remarks Time O(1), Space O(1)
* @param v - New left node, or null/undefined.
* @returns void
*/
set left(v) {
if (v) {
v.parent = this;
}
this._left = v;
}
/**
* Get the right child pointer.
* @remarks Time O(1), Space O(1)
* @returns Right child node, or null/undefined.
*/
get right() {
return this._right;
}
/**
* Set the right child and update its parent pointer.
* @remarks Time O(1), Space O(1)
* @param v - New right node, or null/undefined.
* @returns void
*/
set right(v) {
if (v) {
v.parent = this;
}
this._right = v;
}
/**
* Gets the height of the node (used in self-balancing trees).
* @remarks Time O(1), Space O(1)
*
* @returns The height.
*/
/* istanbul ignore next -- covered by AVLTree tests (subclass uses height) */
get height() {
return this._height;
}
/* istanbul ignore next -- covered by AVLTree tests (subclass uses height) */
set height(value) {
this._height = value;
}
/**
* Gets the color of the node (used in Red-Black trees).
* @remarks Time O(1), Space O(1)
*
* @returns The node's color.
*/
get color() {
return this._color;
}
/**
* Sets the color of the node.
* @remarks Time O(1), Space O(1)
*
* @param value - The new color.
*/
set color(value) {
this._color = value;
}
/**
* Gets the count of nodes in the subtree rooted at this node (used in order-statistic trees).
* @remarks Time O(1), Space O(1)
*
* @returns The subtree node count.
*/
/* istanbul ignore next -- internal field, exercised indirectly via tree operations */
get count() {
return this._count;
}
/**
* Gets the position of the node relative to its parent.
* @remarks Time O(1), Space O(1)
*
* @returns The family position (e.g., 'ROOT', 'LEFT', 'RIGHT').
*/
get familyPosition() {
if (!this.parent) {
return this.left || this.right ? "ROOT" : "ISOLATED";
}
if (this.parent.left === this) {
return this.left || this.right ? "ROOT_LEFT" : "LEFT";
} else if (this.parent.right === this) {
return this.left || this.right ? "ROOT_RIGHT" : "RIGHT";
}
return "MAL_NODE";
}
};
var RedBlackTree = class extends BST {
constructor(keysNodesEntriesOrRaws = [], options) {
super([], options);
__publicField(this, "_root");
/**
* (Internal) Header sentinel:
* - header.parent -> root
* - header._left -> min (or NIL)
* - header._right -> max (or NIL)
*
* IMPORTANT:
* - This header is NOT part of the actual tree.
* - Do NOT use `header.left` / `header.right` accessors for wiring: those setters update `NIL.parent`
* and can corrupt sentinel invariants / cause hangs. Only touch `header._left/_right`.
*/
__publicField(this, "_header");
/**
* (Internal) Cache of the current minimum and maximum nodes.
* Used for fast-path insert/update when keys are monotonic or near-boundary.
*/
__publicField(this, "_minNode");
__publicField(this, "_maxNode");
this._root = this.NIL;
this._header = new RedBlackTreeNode(void 0, void 0, "BLACK");
this._header.parent = this.NIL;
this._header._left = this.NIL;
this._header._right = this.NIL;
if (keysNodesEntriesOrRaws) {
this.setMany(keysNodesEntriesOrRaws);
}
}
/**
* Get the current root node.
* @remarks Time O(1), Space O(1)
* @returns Root node, or undefined.
*/
get root() {
return this._root;
}
/**
* Create a red-black node for the given key/value (value ignored in map mode).
* @remarks Time O(1), Space O(1)
* @param key - See parameter type for details.
* @param [value] - See parameter type for details.
* @param color - See parameter type for details.
* @returns A new RedBlackTreeNode instance.
*/
createNode(key, value, color = "BLACK") {
return new RedBlackTreeNode(key, value, color);
}
/**
* Type guard: check whether the input is a RedBlackTreeNode.
* @remarks Time O(1), Space O(1)
* @param keyNodeOrEntry - See parameter type for details.
* @returns True if the value is a RedBlackTreeNode.
*/
isNode(keyNodeOrEntry) {
return keyNodeOrEntry instanceof RedBlackTreeNode;
}
/**
* Remove all nodes, clear the key→value store (if in map mode) and internal caches.
* @remarks Time O(n), Space O(1)
* @example
* // Remove all entries
* const rbt = new RedBlackTree<number>([1, 2, 3]);
* rbt.clear();
* console.log(rbt.isEmpty()); // true;
*/
clear() {
super.clear();
this._root = this.NIL;
this._header.parent = this.NIL;
this._setMinCache(void 0);
this._setMaxCache(void 0);
}
/**
* (Internal) Find a node by key using a tight BST walk (no allocations).
*
* NOTE: This uses `header.parent` as the canonical root pointer.
* @remarks Time O(log n) average, Space O(1)
*/
_findNodeByKey(key) {
var _a, _b, _c;
const NIL = this.NIL;
const cmp = this._compare.bind(this);
let cur = (_a = this._header.parent) != null ? _a : NIL;
while (cur !== NIL) {
const c = cmp(key, cur.key);
if (c < 0) cur = (_b = cur.left) != null ? _b : NIL;
else if (c > 0) cur = (_c = cur.right) != null ? _c : NIL;
else return cur;
}
return void 0;
}
/**
* (Internal) In-order predecessor of a node in a BST.
* @remarks Time O(log n) average, Space O(1)
*/
_predecessorOf(node) {
const NIL = this.NIL;
if (node.left && node.left !== NIL) {
let cur2 = node.left;
while (cur2.right && cur2.right !== NIL) cur2 = cur2.right;
return cur2;
}
let cur = node;
let p = node.parent;
while (p && cur === p.left) {
cur = p;
p = p.parent;
}
return p;
}
/**
* (Internal) In-order successor of a node in a BST.
* @remarks Time O(log n) average, Space O(1)
*/
_successorOf(node) {
const NIL = this.NIL;
if (node.right && node.right !== NIL) {
let cur2 = node.right;
while (cur2.left && cur2.left !== NIL) cur2 = cur2.left;
return cur2;
}
let cur = node;
let p = node.parent;
while (p && cur === p.right) {
cur = p;
p = p.parent;
}
return p;
}
/**
* (Internal) Attach a new node directly under a known parent/side (no search).
*
* This is a performance-oriented helper used by boundary fast paths and hinted insertion.
* It will:
* - wire parent/child pointers (using accessors, so parent pointers are updated)
* - initialize children to NIL
* - mark the new node RED, then run insert fix-up
*
* Precondition: the chosen slot (parent.left/parent.right) is empty (NIL/null/undefined).
* @remarks Time O(log n) average, Space O(1)
*/
_attachNewNode(parent, side, node) {
const NIL = this.NIL;
node.parent = parent;
if (side === "left") parent.left = node;
else parent.right = node;
node.left = NIL;
node.right = NIL;
node.color = "RED";
this._updateCountAlongPath(node);
this._insertFixup(node);
if (this.isRealNode(this._root)) this._root.color = "BLACK";
}
/**
* (Internal) a single source of truth for min/max is header._left/_right.
* Keep legacy _minNode/_maxNode mirrored for compatibility.
* @remarks Time O(1), Space O(1)
*/
/**
* (Internal) Update min cache pointers (header._left is the canonical min pointer).
* @remarks Time O(1), Space O(1)
*/
_setMinCache(node) {
this._minNode = node;
this._header._left = node != null ? node : this.NIL;
}
/**
* (Internal) Update max cache pointers (header._right is the canonical max pointer).
* @remarks Time O(1), Space O(1)
*/
_setMaxCache(node) {
this._maxNode = node;
this._header._right = node != null ? node : this.NIL;
}
/**
* (Internal) Core set implementation returning the affected node.
*
* Hot path goals:
* - Avoid double walks (search+insert): do a single traversal that either updates or inserts.
* - Use header min/max caches to fast-path boundary inserts.
* - Keep header._left/_right as canonical min/max pointers.
*
* Return value:
* - `{ node, created:false }` when an existing key is updated
* - `{ node, created:true }` when a new node is inserted
* - `undefined` only on unexpected internal failure.
* @remarks Time O(log n) average, Space O(1)
*/
_setKVNode(key, nextValue) {
var _a, _b, _c, _d, _e, _f, _g;
const NIL = this.NIL;
const comparator = this._comparator;
const header = this._header;
const minN = (_a = header._left) != null ? _a : NIL;
if (minN !== NIL) {
const cMin = comparator(key, minN.key);
if (cMin === 0) {
minN.value = nextValue;
if (this._isMapMode) this._store.set(key, minN);
return { node: minN, created: false };
}
const minL = minN.left;
if (cMin < 0 && (minL === NIL || minL === null || minL === void 0)) {
const newNode2 = this.createNode(key, nextValue);
this._attachNewNode(minN, "left", newNode2);
if (this._isMapMode) this._store.set(newNode2.key, newNode2);
this._size++;
this._setMinCache(newNode2);
if (header._right === NIL) this._setMaxCache(newNode2);
return { node: newNode2, created: true };
}
if (cMin > 0) {
const maxN = (_b = header._right) != null ? _b : NIL;
const cMax = comparator(key, maxN.key);
if (cMax === 0) {
maxN.value = nextValue;
if (this._isMapMode) this._store.set(key, maxN);
return { node: maxN, created: false };
}
const maxR = maxN.right;
if (cMax > 0 && (maxR === NIL || maxR === null || maxR === void 0)) {
const newNode2 = this.createNode(key, nextValue);
this._attachNewNode(maxN, "right", newNode2);
if (this._isMapMode) this._store.set(newNode2.key, newNode2);
this._size++;
this._setMaxCache(newNode2);
if (header._left === NIL) this._setMinCache(newNode2);
return { node: newNode2, created: true };
}
}
}
const cmp = comparator;
const isMapMode = this._isMapMode;
const store = this._store;
let current = (_c = this._header.parent) != null ? _c : NIL;
let parent;
let lastCompared = 0;
while (current !== NIL) {
parent = current;
lastCompared = cmp(key, current.key);
if (lastCompared < 0) current = (_d = current.left) != null ? _d : NIL;
else if (lastCompared > 0) current = (_e = current.right) != null ? _e : NIL;
else {
current.value = nextValue;
if (isMapMode) store.set(key, current);
return { node: current, created: false };
}
}
const newNode = this.createNode(key, nextValue);
newNode.parent = parent;
if (!parent) {
this._setRoot(newNode);
} else if (lastCompared < 0) {
parent.left = newNode;
} else {
parent.right = newNode;
}
newNode.left = NIL;
newNode.right = NIL;
newNode.color = "RED";
this._updateCountAlongPath(newNode);
this._insertFixup(newNode);
if (this.isRealNode(this._root)) this._root.color = "BLACK";
else return void 0;
if (isMapMode) store.set(newNode.key, newNode);
this._size++;
const hMin = (_f = this._header._left) != null ? _f : NIL;
const hMax = (_g = this._header._right) != null ? _g : NIL;
if (hMin === NIL || hMax === NIL) {
this._setMinCache(newNode);
this._setMaxCache(newNode);
} else if (parent === hMax && lastCompared > 0) {
this._setMaxCache(newNode);
} else if (parent === hMin && lastCompared < 0) {
this._setMinCache(newNode);
} else {
if (cmp(newNode.key, hMin.key) < 0) this._setMinCache(newNode);
if (cmp(newNode.key, hMax.key) > 0) this._setMaxCache(newNode);
}
return { node: newNode, created: true };
}
/**
* (Internal) Boolean wrapper around `_setKVNode`.
*
* Includes a map-mode update fast-path:
* - If `isMapMode=true` and the key already exists in `_store`, then updating the value does not
* require any tree search/rotation (tree shape depends only on key).
* - This path is intentionally limited to `nextValue !== undefined` to preserve existing
* semantics for `undefined` values.
* @remarks Time O(log n) average, Space O(1)
*/
_setKV(key, nextValue) {
if (this._isMapMode) {
const store = this._store;
const node = store.get(key);
if (node) {
node.value = nextValue;
return true;
}
}
return this._setKVNode(key, nextValue) !== void 0;
}
/**
* Insert/update using a hint node to speed up nearby insertions.
*
* close to the expected insertion position (often the previously returned node in a loop).
*
* When the hint is a good fit (sorted / nearly-sorted insertion), this can avoid most of the
* normal root-to-leaf search and reduce constant factors.
*
* When the hint does not match (random workloads), this will fall back to the normal set path.
* @remarks Time O(log n) average, Space O(1)
*/
setWithHintNode(key, value, hint) {
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
if (!hint || !this.isRealNode(hint)) {
return (_a = this._setKVNode(key, value)) == null ? void 0 : _a.node;
}
const cmp = this._compare.bind(this);
const c0 = cmp(key, hint.key);
if (c0 === 0) {
hint.value = value;
if (this._isMapMode) this._store.set(key, hint);
return hint;
}
if (c0 < 0) {
if (!this.isRealNode(hint.left)) {
const newNode = this.createNode(key, value);
if (!this.isRealNode(newNode)) return void 0;
this._attachNewNode(hint, "left", newNode);
if (this._isMapMode) this._store.set(key, newNode);
this._size++;
const NIL = this.NIL;
const hMin = (_b = this._header._left) != null ? _b : NIL;
if (hMin === NIL || this._compare(newNode.key, hMin.key) < 0) this._setMinCache(newNode);
const hMax = (_c = this._header._right) != null ? _c : NIL;
if (hMax === NIL || this._compare(newNode.key, hMax.key) > 0) this._setMaxCache(newNode);
return newNode;
}
const pred = this._predecessorOf(hint);
if (pred && cmp(pred.key, key) >= 0) {
return (_d = this._setKVNode(key, value)) == null ? void 0 : _d.node;
}
if (pred && !this.isRealNode(pred.right)) {
const newNode = this.createNode(key, value);
if (!this.isRealNode(newNode)) return void 0;
this._attachNewNode(pred, "right", newNode);
if (this._isMapMode) this._store.set(key, newNode);
this._size++;
const NIL = this.NIL;
const hMin = (_e = this._header._left) != null ? _e : NIL;
if (hMin === NIL || this._compare(newNode.key, hMin.key) < 0) this._setMinCache(newNode);
const hMax = (_f = this._header._right) != null ? _f : NIL;
if (hMax === NIL || this._compare(newNode.key, hMax.key) > 0) this._setMaxCache(newNode);
return newNode;
}
return (_g = this._setKVNode(key, value)) == null ? void 0 : _g.node;
}
if (!this.isRealNode(hint.right)) {
const newNode = this.createNode(key, value);
if (!this.isRealNode(newNode)) return void 0;
this._attachNewNode(hint, "right", newNode);
if (this._isMapMode) this._store.set(key, newNode);
this._size++;
const NIL = this.NIL;
const hMin = (_h = this._header._left) != null ? _h : NIL;
if (hMin === NIL || this._compare(newNode.key, hMin.key) < 0) this._setMinCache(newNode);
const hMax = (_i = this._header._right) != null ? _i : NIL;
if (hMax === NIL || this._compare(newNode.key, hMax.key) > 0) this._setMaxCache(newNode);
return newNode;
}
const succ = this._successorOf(hint);
if (succ && cmp(succ.key, key) <= 0) {
return (_j = this._setKVNode(key, value)) == null ? void 0 : _j.node;
}
if (succ && !this.isRealNode(succ.left)) {
const newNode = this.createNode(key, value);
if (!this.isRealNode(newNode)) return void 0;
this._attachNewNode(succ, "left", newNode);
if (this._isMapMode) this._store.set(key, newNode);
this._size++;
const NIL = this.NIL;
const hMin = (_k = this._header._left) != null ? _k : NIL;
if (hMin === NIL || this._compare(newNode.key, hMin.key) < 0) this._setMinCache(newNode);
const hMax = (_l = this._header._right) != null ? _l : NIL;
if (hMax === NIL || this._compare(newNode.key, hMax.key) > 0) this._setMaxCache(newNode);
return newNode;
}
return (_m = this._setKVNode(key, value)) == null ? void 0 : _m.node;
}
/**
* Boolean wrapper for setWithHintNode.
* @remarks Time O(log n) average, Space O(1)
*/
setWithHint(key, value, hint) {
return this.setWithHintNode(key, value, hint) !== void 0;
}
/**
* Insert or update a key/value (map mode) or key-only (set mode).
*
* This method is optimized for:
* - monotonic inserts via min/max boundary fast paths
* - updates via a single-pass search (no double walk)
*
* @remarks Time O(log n) average, Space O(1)
* @example
* // basic Red-Black Tree with simple number keys
* // Create a simple Red-Black Tree with numeric keys
* const tree = new RedBlackTree([5, 2, 8, 1, 9]);
*
* tree.print();
* // _2___
* // / \
* // 1 _8_
* // / \
* // 5 9
*
* // Verify the tree maintains sorted order
* console.log([...tree.keys()]); // [1, 2, 5, 8, 9];
*
* // Check size
* console.log(tree.size); // 5;
*/
set(keyNodeOrEntry, value) {
if (!this.isNode(keyNodeOrEntry)) {
if (keyNodeOrEntry === null || keyNodeOrEntry === void 0) return false;
if (this.isEntry(keyNodeOrEntry)) {
const key = keyNodeOrEntry[0];
if (key === null || key === void 0) return false;
const nextValue = value != null ? value : keyNodeOrEntry[1];
return this._setKV(key, nextValue);
}
return this._setKV(keyNodeOrEntry, value);
}
const [newNode, newValue] = this._keyValueNodeOrEntryToNodeAndValue(keyNodeOrEntry, value);
if (!this.isRealNode(newNode)) return false;
const insertStatus = this._insert(newNode);
if (insertStatus === "CREATED") {
if (this.isRealNode(this._root)) {
this._root.color = "BLACK";
} else {
return false;
}
if (this._isMapMode) {
const n = this.getNode(newNode.key);
if (this.isRealNode(n)) {
n.value = newValue;
this._store.set(n.key, n);
}
}
this._size++;
return true;
}
if (insertStatus === "UPDATED") {
if (this._isMapMode) {
const n = this.getNode(newNode.key);
if (this.isRealNode(n)) {
n.value = newValue;
this._store.set(n.key, n);
}
}
return true;
}
return false;
}
/**
* Delete a node by key/node/entry and rebalance as needed.
* @remarks Time O(log n) average, Space O(1)
* @param keyNodeEntryRawOrPredicate - Key, node, or [key, value] entry identifying the node to delete.
* @returns Array with deletion metadata (removed node, rebalancing hint if any).
* @example
* // Remove and rebalance
* const rbt = new RedBlackTree<number>([10, 5, 15, 3, 7]);
* rbt.delete(5);
* console.log(rbt.has(5)); // false;
* console.log(rbt.size); // 4;
*/
delete(keyNodeEntryRawOrPredicate) {
var _a, _b, _c;
if (keyNodeEntryRawOrPredicate === null) return false;
let nodeToDelete;
if (this._isPredicate(keyNodeEntryRawOrPredicate)) nodeToDelete = this.getNode(keyNodeEntryRawOrPredicate);
else nodeToDelete = this.isRealNode(keyNodeEntryRawOrPredicate) ? keyNodeEntryRawOrPredicate : this.getNode(keyNodeEntryRawOrPredicate);
if (!nodeToDelete) {
return false;
}
const willDeleteMin = nodeToDelete === this._minNode;
const willDeleteMax = nodeToDelete === this._maxNode;
const nextMin = willDeleteMin ? this._successorOf(nodeToDelete) : void 0;
const nextMax = willDeleteMax ? this._predecessorOf(nodeToDelete) : void 0;
let originalColor = nodeToDelete.color;
const NIL = this.NIL;
let replacementNode = NIL;
if (!this.isRealNode(nodeToDelete.left)) {
replacementNode = (_a = nodeToDelete.right) != null ? _a : NIL;
this._transplant(nodeToDelete, replacementNode);
} else if (!this.isRealNode(nodeToDelete.right)) {
replacementNode = nodeToDelete.left;
this._transplant(nodeToDelete, replacementNode);
} else {
const successor = this.getLeftMost((node) => node, nodeToDelete.right);
if (successor) {
originalColor = successor.color;
replacementNode = (_b = successor.right) != null ? _b : NIL;
if (successor.parent === nodeToDelete) {
replacementNode.parent = successor;
} else {
this._transplant(successor, replacementNode);
successor.right = nodeToDelete.right;
if (successor.right) {
successor.right.parent = successor;
}
}
this._transplant(nodeToDelete, successor);
successor.left = nodeToDelete.left;
if (successor.left) {
successor.left.parent = successor;
}
successor.color = nodeToDelete.color;
}
}
if (this._isMapMode) this._store.delete(nodeToDelete.key);
this._size--;
this._updateCountAlongPath((_c = replacementNode == null ? void 0 : replacementNode.parent) != null ? _c : replacementNode);
if (this._size <= 0) {
this._setMinCache(void 0);
this._setMaxCache(void 0);
} else {
if (willDeleteMin) this._setMinCache(nextMin);
if (willDeleteMax) this._setMaxCache(nextMax);
if (!this._minNode || !this.isRealNode(this._minNode)) {
this._setMinCache(this.isRealNode(this._root) ? this.getLeftMost((n) => n, this._root) : void 0);
}
if (!this._maxNode || !this.isRealNode(this._maxNode)) {
this._setMaxCache(this.isRealNode(this._root) ? this.getRightMost((n) => n, this._root) : void 0);
}
}
if (originalColor === "BLACK") {
this._deleteFixup(replacementNode);
}
return true;
}
/**
* Transform entries into a like-kind red-black tree with possibly different key/value types.
* @remarks Time O(n) average, Space O(n)
* @template MK
* @template MV
* @template MR
* @param callback - Mapping function from (key, value, index, tree) to a new [key, value].
* @param [options] - See parameter type for details.
* @param [thisArg] - See parameter type for details.
* @returns A new RedBlackTree with mapped entries.
*/
/**
* Red-Black trees are self-balancing — `perfectlyBalance` rebuilds via
* sorted bulk insert, which naturally produces a balanced RBT.
* @remarks Time O(N), Space O(N)
* @example
* // Rebalance tree
* const rbt = new RedBlackTree<number>([1, 2, 3, 4, 5]);
* rbt.perfectlyBalance();
* console.log(rbt.isAVLBalanced()); // true;
*/
perfectlyBalance(_iterationType) {
const entries = [];
for (const [key, value] of this) entries.push([key, value]);
if (entries.length <= 1) return true;
this.clear();
this.setMany(
entries.map(([k]) => k),
entries.map(([, v]) => v),
true
// isBalanceAdd
);
return true;
}
/**
* Transform to new tree
* @example
* // Transform to new tree
* const rbt = new RedBlackTree<number, number>([[1, 10], [2, 20]]);
* const doubled = rbt.map((v, k) => [k, (v ?? 0) * 2] as [number, number]);
* console.log([...doubled.values()]); // [20, 40];
*/
map(callback, options, thisArg) {
const out = this._createLike([], options);
let index = 0;
for (const [key, value] of this) {
out.set(callback.call(thisArg, value, key, index++, this));
}
return out;
}
/**
* (Internal) Create an empty instance of the same concrete tree type.
* @remarks Time O(1) average, Space O(1)
*/
_createInstance(options) {
const Ctor = this.constructor;
return new Ctor([], { ...this._snapshotOptions(), ...options != null ? options : {} });
}
/**
* (Internal) Create a like-kind tree (same concrete class) populated from an iterable.
* @remarks Time O(m log m) average (m = iterable length), Space O(m)
*/
_createLike(iter = [], options) {
const Ctor = this.constructor;
return new Ctor(iter, { ...this._snapshotOptions(), ...options != null ? options : {} });
}
/**
* (Internal) Set the root pointer and keep header.parent in sync.
* @remarks Time O(1), Space O(1)
*/
_setRoot(v) {
const NIL = this.NIL;
if (v) {
v.parent = void 0;
}
this._root = v;
this._header.parent = v != null ? v : NIL;
}
/**
* (Internal) Replace a node in place while preserving its color.
* @remarks Time O(1) average, Space O(1)
*/
_replaceNode(oldNode, newNode) {
newNode.color = oldNode.color;
return super._replaceNode(oldNode, newNode);
}
/**
* (Protected) Standard BST insert followed by red-black fix-up.
* @remarks Time O(log n) average, Space O(1)
* @param node - Node to insert.
* @returns Status string: 'CREATED' or 'UPDATED'.
*/
_insert(node) {
var _a, _b, _c;
const NIL = this.NIL;
const cmp = this._compare.bind(this);
let current = (_a = this._header.parent) != null ? _a : NIL;
let parent;
let lastCompared = 0;
while (current !== NIL) {
parent = current;
lastCompared = cmp(node.key, current.key);
if (lastCompared < 0) {
current = (_b = current.left) != null ? _b : NIL;
} else if (lastCompared > 0) {
current = (_c = current.right) != null ? _c : NIL;
} else {
this._replaceNode(current, node);
return "UPDATED";
}
}
node.parent = parent;
if (!parent) {
this._setRoot(node);
} else if (lastCompared < 0) {
parent.left = node;
} else {
parent.right = node;
}
node.left = NIL;
node.right = NIL;
node.color = "RED";
this._updateCountAlongPath(node);
this._insertFixup(node);
return "CREATED";
}
/**
* (Protected) Transplant a subtree in place of another during deletion.
* @remarks Time O(1), Space O(1)
* @param u - Node to replace.
* @param v - Replacement subtree root (may be undefined).
* @returns void
*/
_transplant(u, v) {
if (!u.parent) {
this._setRoot(v);
} else if (u === u.parent.left) {
u.parent.left = v;
} else {
u.parent.right = v;
}
if (v) {
v.parent = u.parent;
}
}
/**
* (Protected) Restore red-black properties after insertion (recolor/rotate).
* @remarks Time O(log n) average, Space O(1)
* @param z - Recently inserted node.
* @returns void
*/
_insertFixup(z) {
const leftRotate = this._leftRotate.bind(this);
const rightRotate = this._rightRotate.bind(this);
while (z) {
const p = z.parent;
if (!p || p.color !== "RED") break;
const gp = p.parent;
if (!gp) break;
if (p === gp.left) {
const y = gp.right;
if ((y == null ? void 0 : y.color) === "RED") {
p.color = "BLACK";
y.color = "BLACK";
gp.color = "RED";
z = gp;
continue;
}
if (z === p.right) {
z = p;
leftRotate(z);
}
const p2 = z == null ? void 0 : z.parent;
const gp2 = p2 == null ? void 0 : p2.parent;
if (p2 && gp2) {
p2.color = "BLACK";
gp2.color = "RED";
rightRotate(gp2);
}
} else {
const y = gp.left;
if ((y == null ? void 0 : y.color) === "RED") {
p.color = "BLACK";
y.color = "BLACK";
gp.color = "RED";
z = gp;
continue;
}
if (z === p.left) {
z = p;
rightRotate(z);
}
const p2 = z == null ? void 0 : z.parent;
const gp2 = p2 == null ? void 0 : p2.parent;
if (p2 && gp2) {
p2.color = "BLACK";
gp2.color = "RED";
leftRotate(gp2);
}
}
break;
}
if (this.isRealNode(this._root)) this._root.color = "BLACK";
}
/**
* (Protected) Restore red-black properties after deletion (recolor/rotate).
* @remarks Time O(log n) average, Space O(1)
* @param node - Child that replaced the deleted node (may be undefined).
* @returns void
*/
_deleteFixup(node) {
if (!node) return;
const NIL = this.NIL;
let current = node;
while (current !== this.root && current.color === "BLACK") {
const parent = current.parent;
if (!parent) break;
const nodeIsLeft = current === parent.left;
let sibling = nodeIsLeft ? parent.right : parent.left;
if (sibling && sibling.color === "RED") {
sibling.color = "BLACK";
parent.color = "RED";
if (nodeIsLeft) {
this._leftRotate(parent);
sibling = parent.right;
} else {
this._rightRotate(parent);
sibling = parent.left;
}
}
const sibLeft = sibling == null ? void 0 : sibling.left;
const sibRight = sibling == null ? void 0 : sibling.right;
const sibLeftBlack = !sibLeft || sibLeft === NIL || sibLeft.color === "BLACK";
const sibRightBlack = !sibRight || sibRight === NIL || sibRight.color === "BLACK";
if (sibLeftBlack && sibRightBlack) {
if (sibling) sibling.color = "RED";
current = parent;
} else {
if (nodeIsLeft) {
if (sibRightBlack) {
if (sibLeft) sibLeft.color = "BLACK";
if (sibling) sibling.color = "RED";
if (sibling) this._rightRotate(sibling);
sibling = parent.right;
}
if (sibling) sibling.color = parent.color;
parent.color = "BLACK";
if (sibling == null ? void 0 : sibling.right) sibling.right.color = "BLACK";
this._leftRotate(parent);
} else {
if (sibLeftBlack) {
if (sibRight) sibRight.color = "BLACK";
if (sibling) sibling.color = "RED";
if (sibling) this._leftRotate(sibling);
sibling = parent.left;
}
if (sibling) sibling.color = parent.color;
parent.color = "BLACK";
if (sibling == null ? void 0 : sibling.left) sibling.left.color = "BLACK";
this._rightRotate(parent);
}
current = this.root;
}
}
current.color = "BLACK";
}
/**
* (Protected) Perform a left rotation around x.
* @remarks Time O(1), Space O(1)
* @param x - Pivot node to rotate around.
* @returns void
*/
_leftRotate(x) {
if (!x || !x.right) {
return;
}
const y = x.right;
x.right = y.left;
if (y.left && y.left !== this.NIL) {
y.left.parent = x;
}
y.parent = x.parent;
if (!x.parent) {
this._setRoot(y);
} else if (x === x.parent.left) {
x.parent.left = y;
} else {
x.parent.right = y;
}
y.left = x;
x.parent = y;
this._updateCount(x);
this._updateCount(y);
}
/**
* (Protected) Perform a right rotation around y.
* @remarks Time O(1), Space O(1)
* @param y - Pivot node to rotate around.
* @returns void
*/
_rightRotate(y) {
if (!y || !y.left) {
return;
}
const x = y.left;
y.left = x.right;
if (x.right && x.right !== this.NIL) {
x.right.parent = y;
}
x.parent = y.parent;
if (!y.parent) {
this._setRoot(x);
} else if (y === y.parent.left) {
y.parent.left = x;
} else {
y.parent.right = x;
}
x.right = y;
y.parent = x;
this._updateCount(y);
this._updateCount(x);
}
};
return __toCommonJS(src_exports);
})();
/**
* data-structure-typed
*
* @author Pablo Zeng
* @copyright Copyright (c) 2022 Pablo Zeng <zrwusa@gmail.com>
* @license MIT License
*/
//# sourceMappingURL=red-black-tree-typed.js.map