UNPKG

avl-tree-typed

Version:
880 lines (879 loc) 30.7 kB
"use strict"; /** * data-structure-typed * * @author Pablo Zeng * @copyright Copyright (c) 2022 Pablo Zeng <zrwusa@gmail.com> * @license MIT License */ Object.defineProperty(exports, "__esModule", { value: true }); exports.Deque = void 0; const utils_1 = require("../../utils"); const linear_base_1 = require("../base/linear-base"); /** * Deque implemented with circular buckets allowing O(1) amortized push/pop at both ends. * @remarks Time O(1), Space O(1) * @template E * @template R * 1. Operations at Both Ends: Supports adding and removing elements at both the front and back of the queue. This allows it to be used as a stack (last in, first out) and a queue (first in, first out). * 2. Efficient Random Access: Being based on an array, it offers fast random access capability, allowing constant time access to any element. * 3. Continuous Memory Allocation: Since it is based on an array, all elements are stored contiguously in memory, which can bring cache friendliness and efficient memory access. * 4. Efficiency: Adding and removing elements at both ends of a deque is usually very fast. However, when the dynamic array needs to expand, it may involve copying the entire array to a larger one, and this operation has a time complexity of O(n). * 5. Performance jitter: Deque may experience performance jitter, but DoublyLinkedList will not * @example * // prize roulette * class PrizeRoulette { * private deque: Deque<string>; * * constructor(prizes: string[]) { * // Initialize the deque with prizes * this.deque = new Deque<string>(prizes); * } * * // Rotate clockwise to the right (forward) * rotateClockwise(steps: number): void { * const n = this.deque.length; * if (n === 0) return; * * for (let i = 0; i < steps; i++) { * const last = this.deque.pop(); // Remove the last element * this.deque.unshift(last!); // Add it to the front * } * } * * // Rotate counterclockwise to the left (backward) * rotateCounterClockwise(steps: number): void { * const n = this.deque.length; * if (n === 0) return; * * for (let i = 0; i < steps; i++) { * const first = this.deque.shift(); // Remove the first element * this.deque.push(first!); // Add it to the back * } * } * * // Display the current prize at the head * display() { * return this.deque.first; * } * } * * // Example usage * const prizes = ['Car', 'Bike', 'Laptop', 'Phone', 'Watch', 'Headphones']; // Initialize the prize list * const roulette = new PrizeRoulette(prizes); * * // Display the initial state * console.log(roulette.display()); // 'Car' // Car * * // Rotate clockwise by 3 steps * roulette.rotateClockwise(3); * console.log(roulette.display()); // 'Phone' // Phone * * // Rotate counterclockwise by 2 steps * roulette.rotateCounterClockwise(2); * console.log(roulette.display()); // 'Headphones' * @example * // sliding window * // Maximum function of sliding window * function maxSlidingWindow(nums: number[], k: number): number[] { * const n = nums.length; * if (n * k === 0) return []; * * const deq = new Deque<number>(); * const result: number[] = []; * * for (let i = 0; i < n; i++) { * // Delete indexes in the queue that are not within the window range * if (deq.length > 0 && deq.first! === i - k) { * deq.shift(); * } * * // Remove all indices less than the current value from the tail of the queue * while (deq.length > 0 && nums[deq.last!] < nums[i]) { * deq.pop(); * } * * // Add the current index to the end of the queue * deq.push(i); * * // Add the maximum value of the window to the results * if (i >= k - 1) { * result.push(nums[deq.first!]); * } * } * * return result; * } * * const nums = [1, 3, -1, -3, 5, 3, 6, 7]; * const k = 3; * console.log(maxSlidingWindow(nums, k)); // [3, 3, 5, 5, 6, 7] */ class Deque extends linear_base_1.LinearBase { /** * Create a Deque and optionally bulk-insert elements. * @remarks Time O(N), Space O(N) * @param [elements] - Iterable (or iterable-like) of elements/records to insert. * @param [options] - Options such as bucketSize, toElementFn, and maxLen. * @returns New Deque instance. */ constructor(elements = [], options) { super(options); this._equals = Object.is; this._bucketSize = 1 << 12; this._bucketFirst = 0; this._firstInBucket = 0; this._bucketLast = 0; this._lastInBucket = 0; this._bucketCount = 0; this._buckets = []; this._length = 0; if (options) { const { bucketSize } = options; if (typeof bucketSize === 'number') this._bucketSize = bucketSize; } let _size; if ('length' in elements) { _size = typeof elements.length === 'function' ? elements.length() : elements.length; } else { _size = typeof elements.size === 'function' ? elements.size() : elements.size; } this._bucketCount = (0, utils_1.calcMinUnitsRequired)(_size, this._bucketSize) || 1; for (let i = 0; i < this._bucketCount; ++i) { this._buckets.push(new Array(this._bucketSize)); } const needBucketNum = (0, utils_1.calcMinUnitsRequired)(_size, this._bucketSize); this._bucketFirst = this._bucketLast = (this._bucketCount >> 1) - (needBucketNum >> 1); this._firstInBucket = this._lastInBucket = (this._bucketSize - (_size % this._bucketSize)) >> 1; this.pushMany(elements); } /** * Get the current bucket size. * @remarks Time O(1), Space O(1) * @returns Bucket capacity per bucket. */ get bucketSize() { return this._bucketSize; } /** * Get the index of the first bucket in use. * @remarks Time O(1), Space O(1) * @returns Zero-based bucket index. */ get bucketFirst() { return this._bucketFirst; } /** * Get the index inside the first bucket. * @remarks Time O(1), Space O(1) * @returns Zero-based index within the first bucket. */ get firstInBucket() { return this._firstInBucket; } /** * Get the index of the last bucket in use. * @remarks Time O(1), Space O(1) * @returns Zero-based bucket index. */ get bucketLast() { return this._bucketLast; } /** * Get the index inside the last bucket. * @remarks Time O(1), Space O(1) * @returns Zero-based index within the last bucket. */ get lastInBucket() { return this._lastInBucket; } /** * Get the number of buckets allocated. * @remarks Time O(1), Space O(1) * @returns Bucket count. */ get bucketCount() { return this._bucketCount; } /** * Get the internal buckets array. * @remarks Time O(1), Space O(1) * @returns Array of buckets storing values. */ get buckets() { return this._buckets; } /** * Get the number of elements in the deque. * @remarks Time O(1), Space O(1) * @returns Current length. */ get length() { return this._length; } /** * Get the first element without removing it. * @remarks Time O(1), Space O(1) * @returns First element or undefined. */ get first() { if (this._length === 0) return; return this._buckets[this._bucketFirst][this._firstInBucket]; } /** * Get the last element without removing it. * @remarks Time O(1), Space O(1) * @returns Last element or undefined. */ get last() { if (this._length === 0) return; return this._buckets[this._bucketLast][this._lastInBucket]; } /** * Create a Deque from an array of elements. * @remarks Time O(N), Space O(N) * @template E * @template R * @param this - Constructor (subclass) to instantiate. * @param data - Array of elements to insert in order. * @param [options] - Options forwarded to the constructor. * @returns A new Deque populated from the array. */ static fromArray(data, options) { return new this(data, options); } /** * Append one element at the back. * @remarks Time O(1) amortized, Space O(1) * @param element - Element to append. * @returns True when appended. */ push(element) { if (this._length) { if (this._lastInBucket < this._bucketSize - 1) { this._lastInBucket += 1; } else if (this._bucketLast < this._bucketCount - 1) { this._bucketLast += 1; this._lastInBucket = 0; } else { this._bucketLast = 0; this._lastInBucket = 0; } if (this._bucketLast === this._bucketFirst && this._lastInBucket === this._firstInBucket) this._reallocate(); } this._length += 1; this._buckets[this._bucketLast][this._lastInBucket] = element; if (this._maxLen > 0 && this._length > this._maxLen) this.shift(); return true; } /** * Remove and return the last element. * @remarks Time O(1), Space O(1) * @returns Removed element or undefined. */ pop() { if (this._length === 0) return; const element = this._buckets[this._bucketLast][this._lastInBucket]; if (this._length !== 1) { if (this._lastInBucket > 0) { this._lastInBucket -= 1; } else if (this._bucketLast > 0) { this._bucketLast -= 1; this._lastInBucket = this._bucketSize - 1; } else { this._bucketLast = this._bucketCount - 1; this._lastInBucket = this._bucketSize - 1; } } this._length -= 1; return element; } /** * Remove and return the first element. * @remarks Time O(1) amortized, Space O(1) * @returns Removed element or undefined. */ shift() { if (this._length === 0) return; const element = this._buckets[this._bucketFirst][this._firstInBucket]; if (this._length !== 1) { if (this._firstInBucket < this._bucketSize - 1) { this._firstInBucket += 1; } else if (this._bucketFirst < this._bucketCount - 1) { this._bucketFirst += 1; this._firstInBucket = 0; } else { this._bucketFirst = 0; this._firstInBucket = 0; } } this._length -= 1; return element; } /** * Prepend one element at the front. * @remarks Time O(1) amortized, Space O(1) * @param element - Element to prepend. * @returns True when prepended. */ unshift(element) { if (this._length) { if (this._firstInBucket > 0) { this._firstInBucket -= 1; } else if (this._bucketFirst > 0) { this._bucketFirst -= 1; this._firstInBucket = this._bucketSize - 1; } else { this._bucketFirst = this._bucketCount - 1; this._firstInBucket = this._bucketSize - 1; } if (this._bucketFirst === this._bucketLast && this._firstInBucket === this._lastInBucket) this._reallocate(); } this._length += 1; this._buckets[this._bucketFirst][this._firstInBucket] = element; if (this._maxLen > 0 && this._length > this._maxLen) this.pop(); return true; } /** * Append a sequence of elements. * @remarks Time O(N), Space O(1) * @param elements - Iterable (or iterable-like) of elements/records. * @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. * @remarks Time O(N), Space O(1) * @param [elements] - Iterable (or iterable-like) of elements/records. * @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; } /** * Check whether the deque is empty. * @remarks Time O(1), Space O(1) * @returns True if length is 0. */ isEmpty() { return this._length === 0; } /** * Remove all elements and reset structure. * @remarks Time O(1), Space O(1) * @returns void */ clear() { this._buckets = [new Array(this._bucketSize)]; this._bucketCount = 1; this._bucketFirst = this._bucketLast = this._length = 0; this._firstInBucket = this._lastInBucket = this._bucketSize >> 1; } /** * Get the element at a given position. * @remarks Time O(1), Space O(1) * @param pos - Zero-based position from the front. * @returns Element or undefined. */ at(pos) { if (pos < 0 || pos >= this._length) return undefined; const { bucketIndex, indexInBucket } = this._getBucketAndPosition(pos); return this._buckets[bucketIndex][indexInBucket]; } /** * Replace the element at a given position. * @remarks Time O(1), Space O(1) * @param pos - Zero-based position from the front. * @param element - New element value. * @returns True if updated. */ setAt(pos, element) { (0, utils_1.rangeCheck)(pos, 0, this._length - 1); const { bucketIndex, indexInBucket } = this._getBucketAndPosition(pos); this._buckets[bucketIndex][indexInBucket] = element; return true; } /** * Insert repeated copies of an element at a position. * @remarks Time O(N), Space O(1) * @param pos - Zero-based position from the front. * @param element - Element to insert. * @param [num] - Number of times to insert (default 1). * @returns True if inserted. */ addAt(pos, element, num = 1) { const length = this._length; (0, utils_1.rangeCheck)(pos, 0, length); if (pos === 0) { while (num--) this.unshift(element); } else if (pos === this._length) { while (num--) this.push(element); } else { const arr = []; for (let i = pos; i < this._length; ++i) { const v = this.at(i); if (v !== undefined) arr.push(v); } this.cut(pos - 1, true); for (let i = 0; i < num; ++i) this.push(element); for (let i = 0; i < arr.length; ++i) this.push(arr[i]); } return true; } /** * Cut the deque to keep items up to index; optionally mutate in-place. * @remarks Time O(N), Space O(1) * @param pos - Last index to keep. * @param [isCutSelf] - When true, mutate this deque; otherwise return a new deque. * @returns This deque if in-place; otherwise a new deque of the prefix. */ cut(pos, isCutSelf = false) { if (isCutSelf) { if (pos < 0) { this.clear(); return this; } const { bucketIndex, indexInBucket } = this._getBucketAndPosition(pos); this._bucketLast = bucketIndex; this._lastInBucket = indexInBucket; this._length = pos + 1; return this; } else { const newDeque = this._createInstance({ toElementFn: this._toElementFn, maxLen: this._maxLen }); newDeque._setBucketSize(this._bucketSize); for (let i = 0; i <= pos; i++) { const v = this.at(i); if (v !== undefined) newDeque.push(v); } return newDeque; } } /** * 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: length - start). * @param [items] - Elements to insert after `start`. * @returns A new deque containing the removed elements (typed as `this`). */ splice(start, deleteCount = this._length - start, ...items) { (0, utils_1.rangeCheck)(start, 0, this._length); if (deleteCount < 0) deleteCount = 0; if (start + deleteCount > this._length) deleteCount = this._length - start; const removed = this._createInstance({ toElementFn: this._toElementFn, maxLen: this._maxLen }); removed._setBucketSize(this._bucketSize); for (let i = 0; i < deleteCount; i++) { const v = this.at(start + i); if (v !== undefined) removed.push(v); } const tail = []; for (let i = start + deleteCount; i < this._length; i++) { const v = this.at(i); if (v !== undefined) tail.push(v); } this.cut(start - 1, true); for (const it of items) this.push(it); for (const v of tail) this.push(v); return removed; } /** * Cut the deque to keep items from index onward; optionally mutate in-place. * @remarks Time O(N), Space O(1) * @param pos - First index to keep. * @param [isCutSelf] - When true, mutate this deque; otherwise return a new deque. * @returns This deque if in-place; otherwise a new deque of the suffix. */ cutRest(pos, isCutSelf = false) { if (isCutSelf) { if (pos < 0) { return this; } const { bucketIndex, indexInBucket } = this._getBucketAndPosition(pos); this._bucketFirst = bucketIndex; this._firstInBucket = indexInBucket; this._length = this._length - pos; return this; } else { const newDeque = this._createInstance({ toElementFn: this._toElementFn, maxLen: this._maxLen }); newDeque._setBucketSize(this._bucketSize); if (pos < 0) pos = 0; for (let i = pos; i < this._length; i++) { const v = this.at(i); if (v !== undefined) newDeque.push(v); } return newDeque; } } /** * Delete the element at a given position. * @remarks Time O(N), Space O(1) * @param pos - Zero-based position from the front. * @returns Removed element or undefined. */ deleteAt(pos) { (0, utils_1.rangeCheck)(pos, 0, this._length - 1); let deleted; if (pos === 0) { return this.shift(); } else if (pos === this._length - 1) { deleted = this.last; this.pop(); return deleted; } else { const length = this._length - 1; const { bucketIndex: targetBucket, indexInBucket: targetPointer } = this._getBucketAndPosition(pos); deleted = this._buckets[targetBucket][targetPointer]; for (let i = pos; i < length; i++) { const { bucketIndex: curBucket, indexInBucket: curPointer } = this._getBucketAndPosition(i); const { bucketIndex: nextBucket, indexInBucket: nextPointer } = this._getBucketAndPosition(i + 1); this._buckets[curBucket][curPointer] = this._buckets[nextBucket][nextPointer]; } this.pop(); return deleted; } } /** * Delete the first occurrence of a value. * @remarks Time O(N), Space O(1) * @param element - Element to remove (using the configured equality). * @returns True if an element was removed. */ delete(element) { const size = this._length; if (size === 0) return false; let i = 0; let index = 0; while (i < size) { const oldElement = this.at(i); if (!this._equals(oldElement, element)) { this.setAt(index, oldElement); index += 1; } i += 1; } this.cut(index - 1, true); return true; } /** * Delete the first element matching a predicate. * @remarks Time O(N), Space O(1) * @param predicate - Function (value, index, deque) → boolean. * @returns True if a match was removed. */ deleteWhere(predicate) { for (let i = 0; i < this._length; i++) { const v = this.at(i); if (predicate(v, i, this)) { this.deleteAt(i); return true; } } return false; } /** * Set the equality comparator used by delete operations. * @remarks Time O(1), Space O(1) * @param equals - Equality predicate (a, b) → boolean. * @returns This deque. */ setEquality(equals) { this._equals = equals; return this; } /** * Reverse the deque by reversing buckets and pointers. * @remarks Time O(N), Space O(N) * @returns This deque. */ reverse() { this._buckets.reverse().forEach(function (bucket) { bucket.reverse(); }); const { _bucketFirst, _bucketLast, _firstInBucket, _lastInBucket } = this; this._bucketFirst = this._bucketCount - _bucketLast - 1; this._bucketLast = this._bucketCount - _bucketFirst - 1; this._firstInBucket = this._bucketSize - _lastInBucket - 1; this._lastInBucket = this._bucketSize - _firstInBucket - 1; return this; } /** * Deduplicate consecutive equal elements in-place. * @remarks Time O(N), Space O(1) * @returns This deque. */ unique() { if (this._length <= 1) { return this; } let index = 1; let prev = this.at(0); for (let i = 1; i < this._length; ++i) { const cur = this.at(i); if (!this._equals(cur, prev)) { prev = cur; this.setAt(index++, cur); } } this.cut(index - 1, true); return this; } /** * Trim unused buckets to fit exactly the active range. * @remarks Time O(N), Space O(1) * @returns void */ shrinkToFit() { if (this._length === 0) return; const newBuckets = []; if (this._bucketFirst === this._bucketLast) return; else if (this._bucketFirst < this._bucketLast) { for (let i = this._bucketFirst; i <= this._bucketLast; ++i) { newBuckets.push(this._buckets[i]); } } else { for (let i = this._bucketFirst; i < this._bucketCount; ++i) { newBuckets.push(this._buckets[i]); } for (let i = 0; i <= this._bucketLast; ++i) { newBuckets.push(this._buckets[i]); } } this._bucketFirst = 0; this._bucketLast = newBuckets.length - 1; this._buckets = newBuckets; } /** * Deep clone this deque, preserving options. * @remarks Time O(N), Space O(N) * @returns A new deque with the same content and options. */ clone() { return this._createLike(this, { bucketSize: this.bucketSize, toElementFn: this.toElementFn, maxLen: this._maxLen }); } /** * Filter elements into a new deque of the same class. * @remarks Time O(N), Space O(N) * @param predicate - Predicate (value, index, deque) → boolean to keep element. * @param [thisArg] - Value for `this` inside the predicate. * @returns A new deque with kept elements. */ filter(predicate, thisArg) { const out = this._createInstance({ toElementFn: this.toElementFn, maxLen: this._maxLen }); out._setBucketSize(this._bucketSize); let index = 0; for (const el of this) { if (predicate.call(thisArg, el, index, this)) out.push(el); index++; } return out; } /** * Map elements into a new deque of the same element type. * @remarks Time O(N), Space O(N) * @param callback - Mapping function (value, index, deque) → newValue. * @param [thisArg] - Value for `this` inside the callback. * @returns A new deque with mapped values. */ mapSame(callback, thisArg) { const out = this._createInstance({ toElementFn: this._toElementFn, maxLen: this._maxLen }); out._setBucketSize(this._bucketSize); let index = 0; for (const v of this) { const mv = thisArg === undefined ? callback(v, index++, this) : callback.call(thisArg, v, index++, this); out.push(mv); } return out; } /** * Map elements into a new deque (possibly different element type). * @remarks Time O(N), Space O(N) * @template EM * @template RM * @param callback - Mapping function (value, index, deque) → newElement. * @param [options] - Options for the output deque (e.g., bucketSize, toElementFn, maxLen). * @param [thisArg] - Value for `this` inside the callback. * @returns A new Deque with mapped elements. */ map(callback, options, thisArg) { const out = this._createLike([], Object.assign(Object.assign({}, (options !== null && options !== void 0 ? options : {})), { bucketSize: this._bucketSize, maxLen: this._maxLen })); let index = 0; for (const el of this) { const mv = thisArg === undefined ? callback(el, index, this) : callback.call(thisArg, el, index, this); out.push(mv); index++; } return out; } /** * (Protected) Set the internal bucket size. * @remarks Time O(1), Space O(1) * @param size - Bucket capacity to assign. * @returns void */ _setBucketSize(size) { this._bucketSize = size; } /** * (Protected) Iterate elements from front to back. * @remarks Time O(N), Space O(1) * @returns Iterator of elements. */ *_getIterator() { for (let i = 0; i < this._length; ++i) { const v = this.at(i); if (v !== undefined) yield v; } } /** * (Protected) Reallocate buckets to make room near the ends. * @remarks Time O(N), Space O(N) * @param [needBucketNum] - How many extra buckets to add; defaults to half of current. * @returns void */ _reallocate(needBucketNum) { const newBuckets = []; const addBucketNum = needBucketNum || this._bucketCount >> 1 || 1; for (let i = 0; i < addBucketNum; ++i) { newBuckets[i] = new Array(this._bucketSize); } for (let i = this._bucketFirst; i < this._bucketCount; ++i) { newBuckets[newBuckets.length] = this._buckets[i]; } for (let i = 0; i < this._bucketLast; ++i) { newBuckets[newBuckets.length] = this._buckets[i]; } newBuckets[newBuckets.length] = [...this._buckets[this._bucketLast]]; this._bucketFirst = addBucketNum; this._bucketLast = newBuckets.length - 1; for (let i = 0; i < addBucketNum; ++i) { newBuckets[newBuckets.length] = new Array(this._bucketSize); } this._buckets = newBuckets; this._bucketCount = newBuckets.length; } /** * (Protected) Translate a logical position to bucket/offset. * @remarks Time O(1), Space O(1) * @param pos - Zero-based position. * @returns An object containing bucketIndex and indexInBucket. */ _getBucketAndPosition(pos) { let bucketIndex; let indexInBucket; const overallIndex = this._firstInBucket + pos; bucketIndex = this._bucketFirst + Math.floor(overallIndex / this._bucketSize); if (bucketIndex >= this._bucketCount) { bucketIndex -= this._bucketCount; } indexInBucket = ((overallIndex + 1) % this._bucketSize) - 1; if (indexInBucket < 0) { indexInBucket = this._bucketSize - 1; } return { bucketIndex, indexInBucket }; } /** * (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 deque instance. */ _createInstance(options) { const Ctor = this.constructor; return new Ctor([], options); } /** * (Protected) Create a like-kind deque seeded by elements. * @remarks Time O(N), Space O(N) * @template T * @template RR * @param [elements] - Iterable used to seed the new deque. * @param [options] - Options forwarded to the constructor. * @returns A like-kind Deque instance. */ _createLike(elements = [], options) { const Ctor = this.constructor; return new Ctor(elements, options); } /** * (Protected) Iterate elements from back to front. * @remarks Time O(N), Space O(1) * @returns Iterator of elements. */ *_getReverseIterator() { for (let i = this._length - 1; i > -1; i--) { const v = this.at(i); if (v !== undefined) yield v; } } } exports.Deque = Deque;