queue-typed
Version:
Queue data structure
2,049 lines (1,432 loc) • 107 kB
JavaScript
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