UNPKG

queue-typed

Version:
2,049 lines (1,432 loc) 107 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); // src/common/error.ts function raise(ErrorClass, message) { throw new ErrorClass(message); } __name(raise, "raise"); var ERR = { // Range / index indexOutOfRange: /* @__PURE__ */ __name((index, min, max, ctx) => `${ctx ? ctx + ": " : ""}Index ${index} is out of range [${min}, ${max}].`, "indexOutOfRange"), invalidIndex: /* @__PURE__ */ __name((ctx) => `${ctx ? ctx + ": " : ""}Index must be an integer.`, "invalidIndex"), // Type / argument invalidArgument: /* @__PURE__ */ __name((reason, ctx) => `${ctx ? ctx + ": " : ""}${reason}`, "invalidArgument"), comparatorRequired: /* @__PURE__ */ __name((ctx) => `${ctx ? ctx + ": " : ""}Comparator is required for non-number/non-string/non-Date keys.`, "comparatorRequired"), invalidKey: /* @__PURE__ */ __name((reason, ctx) => `${ctx ? ctx + ": " : ""}${reason}`, "invalidKey"), notAFunction: /* @__PURE__ */ __name((name, ctx) => `${ctx ? ctx + ": " : ""}${name} must be a function.`, "notAFunction"), invalidEntry: /* @__PURE__ */ __name((ctx) => `${ctx ? ctx + ": " : ""}Each entry must be a [key, value] tuple.`, "invalidEntry"), invalidNaN: /* @__PURE__ */ __name((ctx) => `${ctx ? ctx + ": " : ""}NaN is not a valid key.`, "invalidNaN"), invalidDate: /* @__PURE__ */ __name((ctx) => `${ctx ? ctx + ": " : ""}Invalid Date key.`, "invalidDate"), reduceEmpty: /* @__PURE__ */ __name((ctx) => `${ctx ? ctx + ": " : ""}Reduce of empty structure with no initial value.`, "reduceEmpty"), callbackReturnType: /* @__PURE__ */ __name((expected, got, ctx) => `${ctx ? ctx + ": " : ""}Callback must return ${expected}; got ${got}.`, "callbackReturnType"), // State / operation invalidOperation: /* @__PURE__ */ __name((reason, ctx) => `${ctx ? ctx + ": " : ""}${reason}`, "invalidOperation"), // Matrix matrixDimensionMismatch: /* @__PURE__ */ __name((op) => `Matrix: Dimensions must be compatible for ${op}.`, "matrixDimensionMismatch"), matrixSingular: /* @__PURE__ */ __name(() => "Matrix: Singular matrix, inverse does not exist.", "matrixSingular"), matrixNotSquare: /* @__PURE__ */ __name(() => "Matrix: Must be square for inversion.", "matrixNotSquare"), matrixNotRectangular: /* @__PURE__ */ __name(() => "Matrix: Must be rectangular for transposition.", "matrixNotRectangular"), matrixRowMismatch: /* @__PURE__ */ __name((expected, got) => `Matrix: Expected row length ${expected}, but got ${got}.`, "matrixRowMismatch"), // Order statistic orderStatisticNotEnabled: /* @__PURE__ */ __name((method, ctx) => `${ctx ? ctx + ": " : ""}${method}() requires enableOrderStatistic: true.`, "orderStatisticNotEnabled") }; // src/common/index.ts var DFSOperation = /* @__PURE__ */ ((DFSOperation2) => { DFSOperation2[DFSOperation2["VISIT"] = 0] = "VISIT"; DFSOperation2[DFSOperation2["PROCESS"] = 1] = "PROCESS"; return DFSOperation2; })(DFSOperation || {}); var _Range = class _Range { 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; } }; __name(_Range, "Range"); var Range = _Range; // src/data-structures/base/iterable-element-base.ts var _IterableElementBase = class _IterableElementBase { /** * 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()); } }; __name(_IterableElementBase, "IterableElementBase"); var IterableElementBase = _IterableElementBase; // src/data-structures/base/linear-base.ts var _LinkedListNode = class _LinkedListNode { /** * Initialize a node. * @param value - Element value. * @remarks Time O(1), Space O(1) */ constructor(value) { __publicField(this, "_value"); __publicField(this, "_next"); this._value = value; this._next = void 0; } /** * Element payload getter. * @returns Element value. * @remarks Time O(1), Space O(1) */ get value() { return this._value; } /** * Element payload setter. * @param value - New value. * @remarks Time O(1), Space O(1) */ set value(value) { this._value = value; } /** * Next node getter. * @returns Next node or `undefined`. * @remarks Time O(1), Space O(1) */ get next() { return this._next; } /** * Next node setter. * @param value - Next node or `undefined`. * @remarks Time O(1), Space O(1) */ set next(value) { this._next = value; } }; __name(_LinkedListNode, "LinkedListNode"); var LinkedListNode = _LinkedListNode; 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; } }; __name(_LinearBase, "LinearBase"); var LinearBase = _LinearBase; var _LinearLinkedBase = class _LinearLinkedBase extends LinearBase { constructor(options) { super(options); if (options) { const { maxLen } = options; if (typeof maxLen === "number" && maxLen > 0 && maxLen % 1 === 0) this._maxLen = maxLen; } } /** * Linked-list optimized `indexOf` (forwards scan). * @param searchElement - Value to match. * @param fromIndex - Start position. * @returns Index or `-1`. * @remarks Time O(n), Space O(1) */ indexOf(searchElement, fromIndex = 0) { const iterator = this._getIterator(); let current = iterator.next(); let index = 0; while (index < fromIndex) { current = iterator.next(); index++; } while (!current.done) { if (current.value === searchElement) return index; current = iterator.next(); index++; } return -1; } /** * Linked-list optimized `lastIndexOf` (reverse scan). * @param searchElement - Value to match. * @param fromIndex - Start position. * @returns Index or `-1`. * @remarks Time O(n), Space O(1) */ lastIndexOf(searchElement, fromIndex = this.length - 1) { const iterator = this._getReverseIterator(); let current = iterator.next(); let index = this.length - 1; while (index > fromIndex) { current = iterator.next(); index--; } while (!current.done) { if (current.value === searchElement) return index; current = iterator.next(); index--; } return -1; } /** * Concatenate lists/elements preserving order. * @param items - Elements or `LinearBase` instances. * @returns New list 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; } /** * Slice via forward iteration (no random access required). * @param start - Inclusive start (supports negative index). * @param end - Exclusive end (supports negative index). * @returns New list (`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(); const iterator = this._getIterator(); let current = iterator.next(); let c = 0; while (c < start) { current = iterator.next(); c++; } for (let i = start; i < end; i++) { newList.push(current.value); current = iterator.next(); } return newList; } /** * Splice by walking node iterators from the start index. * @param start - Start index. * @param deleteCount - How many elements to remove. * @param items - Elements to insert after the splice point. * @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, deleteCount); let currentIndex = 0; let currentNode = void 0; let previousNode = void 0; const iterator = this._getNodeIterator(); for (const node of iterator) { if (currentIndex === start) { currentNode = node; break; } previousNode = node; currentIndex++; } for (let i = 0; i < deleteCount && currentNode; i++) { removedList.push(currentNode.value); const nextNode = currentNode.next; this.delete(currentNode); currentNode = nextNode; } for (let i = 0; i < items.length; i++) { if (previousNode) { this.addAfter(previousNode, items[i]); previousNode = previousNode.next; } else { this.addAt(0, items[i]); previousNode = this._getNodeIterator().next().value; } } return removedList; } reduceRight(callbackfn, initialValue) { let accumulator = initialValue != null ? initialValue : 0; let index = this.length - 1; for (const item of this._getReverseIterator()) { accumulator = callbackfn(accumulator, item, index--, this); } return accumulator; } }; __name(_LinearLinkedBase, "LinearLinkedBase"); var LinearLinkedBase = _LinearLinkedBase; // src/data-structures/linked-list/singly-linked-list.ts var _SinglyLinkedListNode = class _SinglyLinkedListNode extends LinkedListNode { /** * Create a list node. * @remarks Time O(1), Space O(1) * @param value - Element value to store. * @returns New node instance. */ constructor(value) { super(value); __publicField(this, "_next"); this._value = value; this._next = void 0; } /** * Get the next node. * @remarks Time O(1), Space O(1) * @returns Next node or undefined. */ get next() { return this._next; } /** * Set the next node. * @remarks Time O(1), Space O(1) * @param value - Next node or undefined. * @returns void */ set next(value) { this._next = value; } }; __name(_SinglyLinkedListNode, "SinglyLinkedListNode"); var SinglyLinkedListNode = _SinglyLinkedListNode; var _SinglyLinkedList = class _SinglyLinkedList extends LinearLinkedBase { /** * Create a SinglyLinkedList and optionally bulk-insert elements. * @remarks Time O(N), Space O(N) * @param [elements] - Iterable of elements or nodes (or raw records if toElementFn is provided). * @param [options] - Options such as maxLen and toElementFn. * @returns New SinglyLinkedList instance. */ constructor(elements = [], options) { super(options); __publicField(this, "_equals", /* @__PURE__ */ __name((a, b) => Object.is(a, b), "_equals")); __publicField(this, "_head"); __publicField(this, "_tail"); __publicField(this, "_length", 0); this.pushMany(elements); } /** * Get the head node. * @remarks Time O(1), Space O(1) * @returns Head node or undefined. */ get head() { return this._head; } /** * Get the tail node. * @remarks Time O(1), Space O(1) * @returns Tail node or undefined. */ get tail() { return this._tail; } /** * Get the number of elements. * @remarks Time O(1), Space O(1) * @returns Current length. */ get length() { return this._length; } /** * Get the first element value. * @remarks Time O(1), Space O(1) * @returns First element or undefined. */ get first() { var _a; return (_a = this.head) == null ? void 0 : _a.value; } /** * Get the last element value. * @remarks Time O(1), Space O(1) * @returns Last element or undefined. */ get last() { var _a; return (_a = this.tail) == null ? void 0 : _a.value; } /** * Create a new list from an iterable of elements. * @remarks Time O(N), Space O(N) * @template E * @template R * @template S * @param this - The constructor (subclass) to instantiate. * @param data - Iterable of elements to insert. * @param [options] - Options forwarded to the constructor. * @returns A new list populated with the iterable's elements. */ static from(data, options) { const list = new this([], options); for (const x of data) list.push(x); return list; } /** * Append an element/node to the tail. * @remarks Time O(1), Space O(1) * @param elementOrNode - Element or node to append. * @returns True when appended. * @example * // basic SinglyLinkedList creation and push operation * // Create a simple SinglyLinkedList with initial values * const list = new SinglyLinkedList([1, 2, 3, 4, 5]); * * // Verify the list maintains insertion order * console.log([...list]); // [1, 2, 3, 4, 5]; * * // Check length * console.log(list.length); // 5; * * // Push a new element to the end * list.push(6); * console.log(list.length); // 6; * console.log([...list]); // [1, 2, 3, 4, 5, 6]; */ push(elementOrNode) { const newNode = this._ensureNode(elementOrNode); if (!this.head) { this._head = this._tail = newNode; } else { this.tail.next = newNode; this._tail = newNode; } this._length++; if (this._maxLen > 0 && this.length > this._maxLen) this.shift(); return true; } /** * Remove and return the tail element. * @remarks Time O(N), Space O(1) * @returns Removed element or undefined. * @example * // SinglyLinkedList pop and shift operations * const list = new SinglyLinkedList<number>([10, 20, 30, 40, 50]); * * // Pop removes from the end * const last = list.pop(); * console.log(last); // 50; * * // Shift removes from the beginning * const first = list.shift(); * console.log(first); // 10; * * // Verify remaining elements * console.log([...list]); // [20, 30, 40]; * console.log(list.length); // 3; */ pop() { var _a; if (!this.head) return void 0; if (this.head === this.tail) { const value2 = this.head.value; this._head = void 0; this._tail = void 0; this._length--; return value2; } let current = this.head; while (current.next && current.next !== this.tail) current = current.next; const value = (_a = this.tail) == null ? void 0 : _a.value; current.next = void 0; this._tail = current; this._length--; return value; } /** * Remove and return the head element. * @remarks Time O(1), Space O(1) * @returns Removed element or undefined. * @example * // Remove from the front * const list = new SinglyLinkedList<number>([10, 20, 30]); * console.log(list.shift()); // 10; * console.log(list.length); // 2; */ shift() { if (!this.head) return void 0; const removed = this.head; this._head = this.head.next; if (!this._head) this._tail = void 0; this._length--; return removed.value; } /** * Prepend an element/node to the head. * @remarks Time O(1), Space O(1) * @param elementOrNode - Element or node to prepend. * @returns True when prepended. * @example * // SinglyLinkedList unshift and forward traversal * const list = new SinglyLinkedList<number>([20, 30, 40]); * * // Unshift adds to the beginning * list.unshift(10); * console.log([...list]); // [10, 20, 30, 40]; * * // Access elements (forward traversal only for singly linked) * const second = list.at(1); * console.log(second); // 20; * * // SinglyLinkedList allows forward iteration only * const elements: number[] = []; * for (const item of list) { * elements.push(item); * } * console.log(elements); // [10, 20, 30, 40]; * * console.log(list.length); // 4; */ unshift(elementOrNode) { const newNode = this._ensureNode(elementOrNode); if (!this.head) { this._head = this._tail = newNode; } else { newNode.next = this.head; this._head = newNode; } this._length++; return true; } /** * Append a sequence of elements/nodes. * @remarks Time O(N), Space O(1) * @param elements - Iterable of elements or nodes (or raw records if toElementFn is provided). * @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; } /** * Prepend a sequence of elements/nodes. * @remarks Time O(N), Space O(1) * @param elements - Iterable of elements or nodes (or raw records if toElementFn is provided). * @returns Array of per-element success flags. */ unshiftMany(elements) { const ans = []; for (const el of elements) { if (this.toElementFn) ans.push(this.unshift(this.toElementFn(el))); else ans.push(this.unshift(el)); } return ans; } /** * Find the first value matching a predicate (by node). * @remarks Time O(N), Space O(1) * @param elementNodeOrPredicate - Element, node, or node predicate to match. * @returns Matched value or undefined. */ search(elementNodeOrPredicate) { const predicate = this._ensurePredicate(elementNodeOrPredicate); let current = this.head; while (current) { if (predicate(current)) return current.value; current = current.next; } return void 0; } /** * Get the element at a given index. * @remarks Time O(N), Space O(1) * @param index - Zero-based index. * @returns Element or undefined. * @example * // Access element by index * const list = new SinglyLinkedList<string>(['a', 'b', 'c', 'd']); * console.log(list.at(0)); // 'a'; * console.log(list.at(2)); // 'c'; * console.log(list.at(3)); // 'd'; */ at(index) { if (index < 0 || index >= this._length) return void 0; let current = this.head; for (let i = 0; i < index && current; i++) current = current.next; return current == null ? void 0 : current.value; } /** * Type guard: check whether the input is a SinglyLinkedListNode. * @remarks Time O(1), Space O(1) * @param elementNodeOrPredicate - Element, node, or predicate. * @returns True if the value is a SinglyLinkedListNode. */ isNode(elementNodeOrPredicate) { return elementNodeOrPredicate instanceof SinglyLinkedListNode; } /** * Get the node reference at a given index. * @remarks Time O(N), Space O(1) * @param index - Zero-based index. * @returns Node or undefined. * @example * // Get node at index * const list = new SinglyLinkedList<string>(['a', 'b', 'c']); * console.log(list.getNodeAt(1)?.value); // 'b'; */ getNodeAt(index) { if (index < 0 || index >= this._length) return void 0; let current = this.head; for (let i = 0; i < index && current; i++) current = current.next; return current; } /** * Delete the element at an index. * @remarks Time O(N), Space O(1) * @param index - Zero-based index. * @returns Removed element or undefined. * @example * // Remove by index * const list = new SinglyLinkedList<string>(['a', 'b', 'c']); * list.deleteAt(1); * console.log(list.toArray()); // ['a', 'c']; */ deleteAt(index) { if (index < 0 || index >= this._length) return void 0; if (index === 0) return this.shift(); const targetNode = this.getNodeAt(index); const prevNode = this._getPrevNode(targetNode); const value = targetNode.value; prevNode.next = targetNode.next; if (targetNode === this.tail) this._tail = prevNode; this._length--; return value; } /** * Delete the first match by value/node. * @remarks Time O(N), Space O(1) * @param [elementOrNode] - Element or node to remove; if omitted/undefined, nothing happens. * @returns True if removed. * @example * // Remove first occurrence * const list = new SinglyLinkedList<number>([1, 2, 3, 2]); * list.delete(2); * console.log(list.toArray()); // [1, 3, 2]; */ delete(elementOrNode) { if (elementOrNode === void 0 || !this.head) return false; const node = this.isNode(elementOrNode) ? elementOrNode : this.getNode(elementOrNode); if (!node) return false; const prevNode = this._getPrevNode(node); if (!prevNode) { this._head = node.next; if (node === this.tail) this._tail = void 0; } else { prevNode.next = node.next; if (node === this.tail) this._tail = prevNode; } this._length--; return true; } /** * Insert a new element/node at an index, shifting following nodes. * @remarks Time O(N), Space O(1) * @param index - Zero-based index. * @param newElementOrNode - Element or node to insert. * @returns True if inserted. * @example * // Insert at index * const list = new SinglyLinkedList<number>([1, 3]); * list.addAt(1, 2); * console.log(list.toArray()); // [1, 2, 3]; */ addAt(index, newElementOrNode) { if (index < 0 || index > this._length) return false; if (index === 0) return this.unshift(newElementOrNode); if (index === this._length) return this.push(newElementOrNode); const newNode = this._ensureNode(newElementOrNode); const prevNode = this.getNodeAt(index - 1); newNode.next = prevNode.next; prevNode.next = newNode; this._length++; return true; } /** * Set the element value at an index. * @remarks Time O(N), Space O(1) * @param index - Zero-based index. * @param value - New value. * @returns True if updated. */ setAt(index, value) { const node = this.getNodeAt(index); if (!node) return false; node.value = value; return true; } /** * Check whether the list is empty. * @remarks Time O(1), Space O(1) * @returns True if length is 0. * @example * // Check empty * console.log(new SinglyLinkedList().isEmpty()); // true; */ isEmpty() { return this._length === 0; } /** * Remove all nodes and reset length. * @remarks Time O(N), Space O(1) * @returns void * @example * // Remove all * const list = new SinglyLinkedList<number>([1, 2, 3]); * list.clear(); * console.log(list.isEmpty()); // true; */ clear() { this._head = void 0; this._tail = void 0; this._length = 0; } /** * Reverse the list in place. * @remarks Time O(N), Space O(1) * @returns This list. * @example * // Reverse the list in-place * const list = new SinglyLinkedList<number>([1, 2, 3, 4]); * list.reverse(); * console.log([...list]); // [4, 3, 2, 1]; */ reverse() { if (!this.head || this.head === this.tail) return this; let prev; let current = this.head; let next; while (current) { next = current.next; current.next = prev; prev = current; current = next; } [this._head, this._tail] = [this.tail, this.head]; return this; } /** * Find a node by value, reference, or predicate. * @remarks Time O(N), Space O(1) * @param [elementNodeOrPredicate] - Element, node, or node predicate to match. * @returns Matching node or undefined. */ getNode(elementNodeOrPredicate) { if (elementNodeOrPredicate === void 0) return; if (this.isNode(elementNodeOrPredicate)) return elementNodeOrPredicate; const predicate = this._ensurePredicate(elementNodeOrPredicate); let current = this.head; while (current) { if (predicate(current)) return current; current = current.next; } return void 0; } /** * Insert a new element/node before an existing one. * @remarks Time O(N), Space O(1) * @param existingElementOrNode - Existing element or node. * @param newElementOrNode - Element or node to insert. * @returns True if inserted. */ addBefore(existingElementOrNode, newElementOrNode) { const existingNode = this.getNode(existingElementOrNode); if (!existingNode) return false; const prevNode = this._getPrevNode(existingNode); const newNode = this._ensureNode(newElementOrNode); if (!prevNode) { newNode.next = this._head; this._head = newNode; if (!this._tail) this._tail = newNode; this._length++; } else { prevNode.next = newNode; newNode.next = existingNode; this._length++; } return true; } /** * Insert a new element/node after an existing one. * @remarks Time O(N), Space O(1) * @param existingElementOrNode - Existing element or node. * @param newElementOrNode - Element or node to insert. * @returns True if inserted. */ addAfter(existingElementOrNode, newElementOrNode) { const existingNode = this.getNode(existingElementOrNode); if (!existingNode) return false; const newNode = this._ensureNode(newElementOrNode); newNode.next = existingNode.next; existingNode.next = newNode; if (existingNode === this.tail) this._tail = newNode; this._length++; return true; } /** * Remove and/or insert elements at a position (array-like behavior). * @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 list 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, deleteCount); const removedList = this._createInstance(); const prevNode = start === 0 ? void 0 : this.getNodeAt(start - 1); let cur = prevNode ? prevNode.next : this.head; let removedCount = 0; while (removedCount < deleteCount && cur) { removedList.push(cur.value); cur = cur.next; removedCount++; } const afterNode = cur; if (prevNode) { prevNode.next = afterNode; } else { this._head = afterNode; } if (!afterNode) this._tail = prevNode; if (items.length > 0) { let firstInserted; let lastInserted; for (const it of items) { const node = this._ensureNode(it); if (!firstInserted) firstInserted = node; if (lastInserted) lastInserted.next = node; lastInserted = node; } if (prevNode) prevNode.next = firstInserted; else this._head = firstInserted; lastInserted.next = afterNode; if (!afterNode) this._tail = lastInserted; } this._length += items.length - removedCount; if (this._length === 0) { this._head = void 0; this._tail = void 0; } return removedList; } /** * Count how many nodes match a value/node/predicate. * @remarks Time O(N), Space O(1) * @param elementOrNode - Element, node, or node predicate to match. * @returns Number of matches in the list. */ countOccurrences(elementOrNode) { const predicate = elementOrPredicate(elementOrNode, this._equals); let count = 0; let current = this.head; while (current) { if (predicate(current)) count++; current = current.next; } return count; } /** * Set the equality comparator used to compare values. * @remarks Time O(1), Space O(1) * @param equals - Equality predicate (a, b) → boolean. * @returns This list. */ setEquality(equals) { this._equals = equals; return this; } /** * Delete the first node whose value matches a predicate. * @remarks Time O(N), Space O(1) * @param predicate - Predicate (value, index, list) → boolean to decide deletion. * @returns True if a node was removed. */ deleteWhere(predicate) { let prev; let current = this.head; let i = 0; while (current) { if (predicate(current.value, i++, this)) { if (!prev) { this._head = current.next; if (current === this._tail) this._tail = void 0; } else { prev.next = current.next; if (current === this._tail) this._tail = prev; } this._length--; return true; } prev = current; current = current.next; } return false; } /** * Deep clone this list (values are copied by reference). * @remarks Time O(N), Space O(N) * @returns A new list with the same element sequence. * @example * // Deep copy * const list = new SinglyLinkedList<number>([1, 2, 3]); * const copy = list.clone(); * copy.pop(); * console.log(list.length); // 3; * console.log(copy.length); // 2; */ clone() { const out = this._createInstance(); for (const v of this) out.push(v); return out; } /** * Filter values into a new list of the same class. * @remarks Time O(N), Space O(N) * @param callback - Predicate (value, index, list) → boolean to keep value. * @param [thisArg] - Value for `this` inside the callback. * @returns A new list with kept values. * @example * // SinglyLinkedList filter and map operations * const list = new SinglyLinkedList<number>([1, 2, 3, 4, 5]); * * // Filter even numbers * const filtered = list.filter(value => value % 2 === 0); * console.log(filtered.length); // 2; * * // Map to double values * const doubled = list.map(value => value * 2); * console.log(doubled.length); // 5; * * // Use reduce to sum * const sum = list.reduce((acc, value) => acc + value, 0); * console.log(sum); // 15; */ filter(callback, thisArg) { const out = this._createInstance(); let index = 0; for (const value of this) if (callback.call(thisArg, value, index++, this)) out.push(value); return out; } /** * Map values into a new list of the same class. * @remarks Time O(N), Space O(N) * @param callback - Mapping function (value, index, list) → newValue. * @param [thisArg] - Value for `this` inside the callback. * @returns A new list with mapped values. */ mapSame(callback, thisArg) { const out = this._createInstance(); let index = 0; for (const value of this) { const mv = thisArg === void 0 ? callback(value, index++, this) : callback.call(thisArg, value, index++, this); out.push(mv); } return out; } /** * Map values into a new list (possibly different element type). * @remarks Time O(N), Space O(N) * @template EM * @template RM * @param callback - Mapping function (value, index, list) → newElement. * @param [options] - Options for the output list (e.g., maxLen, toElementFn). * @param [thisArg] - Value for `this` inside the callback. * @returns A new SinglyLinkedList with mapped values. * @example * // Transform elements * const list = new SinglyLinkedList<number>([1, 2, 3]); * const doubled = list.map(n => n * 2); * console.log([...doubled]); // [2, 4, 6]; */ map(callback, options, thisArg) { const out = this._createLike([], { ...options != null ? options : {}, maxLen: this._maxLen }); let index = 0; for (const value of this) out.push(callback.call(thisArg, value, index++, this)); return out; } /** * (Protected) Create a node from a value. * @remarks Time O(1), Space O(1) * @param value - Value to wrap in a node. * @returns A new SinglyLinkedListNode instance. */ createNode(value) { return new SinglyLinkedListNode(value); } /** * (Protected) Check if input is a node predicate function. * @remarks Time O(1), Space O(1) * @param elementNodeOrPredicate - Element, node, or node predicate. * @returns True if input is a predicate function. */ _isPredicate(elementNodeOrPredicate) { return typeof elementNodeOrPredicate === "function"; } /** * (Protected) Normalize input into a node instance. * @remarks Time O(1), Space O(1) * @param elementOrNode - Element or node. * @returns A SinglyLinkedListNode for the provided input. */ _ensureNode(elementOrNode) { if (this.isNode(elementOrNode)) return elementOrNode; return this.createNode(elementOrNode); } /** * (Protected) Normalize input into a node predicate. * @remarks Time O(1), Space O(1) * @param elementNodeOrPredicate - Element, node, or predicate. * @returns A predicate taking a node and returning true/false. */ _ensurePredicate(elementNodeOrPredicate) { if (this.isNode(elementNodeOrPredicate)) return (node) => node === elementNodeOrPredicate; if (this._isPredicate(elementNodeOrPredicate)) return elementNodeOrPredicat