max-priority-queue-typed
Version:
Max Priority Queue. Javascript & Typescript Data Structure.
926 lines (840 loc) • 30.5 kB
text/typescript
/**
* data-structure-typed
*
* @author Tyler Zeng
* @copyright Copyright (c) 2022 Tyler Zeng <zrwusa@gmail.com>
* @license MIT License
*/
import type { DequeOptions, ElementCallback, IterableWithSizeOrLength } from '../../types';
import { IterableElementBase } from '../base';
import { calcMinUnitsRequired, rangeCheck } from '../../utils';
/**
* 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
*/
export class Deque<E = any, R = any> extends IterableElementBase<E, R, Deque<E, R>> {
/**
* The constructor initializes a Deque object with optional iterable of elements and options.
* @param elements - An iterable object (such as an array or a Set) that contains the initial
* elements to be added to the deque. It can also be an object with a `length` or `size` property
* that represents the number of elements in the iterable object. If no elements are provided, an
* empty deque
* @param {DequeOptions} [options] - The `options` parameter is an optional object that can contain
* configuration options for the deque. In this code, it is used to set the `bucketSize` option,
* which determines the size of each bucket in the deque. If the `bucketSize` option is not provided
* or is not a number
*/
constructor(elements: IterableWithSizeOrLength<E> | IterableWithSizeOrLength<R> = [], options?: DequeOptions<E, R>) {
super(options);
if (options) {
const { bucketSize, maxLen } = options;
if (typeof bucketSize === 'number') this._bucketSize = bucketSize;
if (typeof maxLen === 'number' && maxLen > 0 && maxLen % 1 === 0) this._maxLen = maxLen;
}
let _size: number;
if ('length' in elements) {
if (elements.length instanceof Function) _size = elements.length();
else _size = elements.length;
} else {
if (elements.size instanceof Function) _size = elements.size();
else _size = elements.size;
}
this._bucketCount = calcMinUnitsRequired(_size, this._bucketSize) || 1;
for (let i = 0; i < this._bucketCount; ++i) {
this._buckets.push(new Array(this._bucketSize));
}
const needBucketNum = calcMinUnitsRequired(_size, this._bucketSize);
this._bucketFirst = this._bucketLast = (this._bucketCount >> 1) - (needBucketNum >> 1);
this._firstInBucket = this._lastInBucket = (this._bucketSize - (_size % this._bucketSize)) >> 1;
for (const el of elements) {
if (this.toElementFn) {
this.push(this.toElementFn(el as R));
} else {
this.push(el as E);
}
}
}
protected _bucketSize: number = 1 << 12;
/**
* The bucketSize function returns the size of the bucket.
*
* @return The size of the bucket
*/
get bucketSize() {
return this._bucketSize;
}
protected _maxLen: number = -1;
/**
* The maxLen function returns the max length of the deque.
*
* @return The max length of the deque
*/
get maxLen() {
return this._maxLen;
}
protected _bucketFirst = 0;
/**
* The function returns the value of the protected variable `_bucketFirst`.
* @returns The value of the `_bucketFirst` property.
*/
get bucketFirst(): number {
return this._bucketFirst;
}
protected _firstInBucket = 0;
/**
* The function returns the value of the protected variable _firstInBucket.
* @returns The method is returning the value of the variable `_firstInBucket`, which is of type
* `number`.
*/
get firstInBucket(): number {
return this._firstInBucket;
}
protected _bucketLast = 0;
/**
* The function returns the value of the protected variable `_bucketLast`.
* @returns The value of the `_bucketLast` property, which is a number.
*/
get bucketLast(): number {
return this._bucketLast;
}
protected _lastInBucket = 0;
/**
* The function returns the value of the protected variable _lastInBucket.
* @returns The method is returning the value of the variable `_lastInBucket`, which is of type
* `number`.
*/
get lastInBucket(): number {
return this._lastInBucket;
}
protected _bucketCount = 0;
/**
* The function returns the number of buckets.
* @returns The number of buckets.
*/
get bucketCount(): number {
return this._bucketCount;
}
protected _buckets: E[][] = [];
/**
* The buckets function returns the buckets property of the object.
* @return The buckets property
*/
get buckets() {
return this._buckets;
}
protected _size = 0;
/**
* The size function returns the number of items in the stack.
* @return The number of values in the set
*/
get size() {
return this._size;
}
/**
* The function returns the first element in a collection if it exists, otherwise it returns
* undefined.
* @returns The first element of the collection, of type E, is being returned.
*/
get first(): E | undefined {
if (this.size === 0) return;
return this._buckets[this._bucketFirst][this._firstInBucket];
}
/**
* The last function returns the last element in the queue.
* @return The last element in the array
*/
get last(): E | undefined {
if (this.size === 0) return;
return this._buckets[this._bucketLast][this._lastInBucket];
}
/**
* Time Complexity - Amortized O(1) (possible reallocation)
* Space Complexity - O(n) (due to potential resizing).
*/
/**
* Time Complexity - Amortized O(1) (possible reallocation),
* Space Complexity - O(n) (due to potential resizing).
*
* The push function adds an element to a data structure and reallocates memory if necessary.
* @param {E} element - The `element` parameter represents the value that you want to add to the data
* structure.
* @returns The size of the data structure after the element has been pushed.
*/
push(element: E): boolean {
if (this.size) {
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._size += 1;
this._buckets[this._bucketLast][this._lastInBucket] = element;
if (this._maxLen > 0 && this._size > this._maxLen) this.shift();
return true;
}
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*/
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* The `pop()` function removes and returns the last element from a data structure, updating the
* internal state variables accordingly.
* @returns The element that was removed from the data structure is being returned.
*/
pop(): E | undefined {
if (this.size === 0) return;
const element = this._buckets[this._bucketLast][this._lastInBucket];
if (this.size !== 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._size -= 1;
return element;
}
/**
* Time Complexity: Amortized O(1)
* Space Complexity: O(n)
*/
/**
* Time Complexity: Amortized O(1)
* Space Complexity: O(n)
*
* The `unshift` function adds an element to the beginning of an array-like data structure and
* returns the new size of the structure.
* @param {E} element - The `element` parameter represents the element that you want to add to the
* beginning of the data structure.
* @returns The size of the data structure after the element has been added.
*/
unshift(element: E): boolean {
if (this.size) {
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._size += 1;
this._buckets[this._bucketFirst][this._firstInBucket] = element;
if (this._maxLen > 0 && this._size > this._maxLen) this.pop();
return true;
}
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*/
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* The `shift()` function removes and returns the first element from a data structure, updating the
* internal state variables accordingly.
* @returns The element that is being removed from the beginning of the data structure is being
* returned.
*/
shift(): E | undefined {
if (this.size === 0) return;
const element = this._buckets[this._bucketFirst][this._firstInBucket];
if (this.size !== 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._size -= 1;
return element;
}
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*/
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* The function checks if the size of an object is equal to zero and returns a boolean value.
* @returns A boolean value indicating whether the size of the object is 0 or not.
*/
isEmpty(): boolean {
return this.size === 0;
}
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*/
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* The clear() function resets the state of the object by initializing all variables to their default
* values.
*/
clear(): void {
this._buckets = [new Array(this._bucketSize)];
this._bucketCount = 1;
this._bucketFirst = this._bucketLast = this._size = 0;
this._firstInBucket = this._lastInBucket = this._bucketSize >> 1;
}
/**
* The below function is a generator that yields elements from a collection one by one.
*/
*begin(): Generator<E> {
let index = 0;
while (index < this.size) {
yield this.at(index);
index++;
}
}
/**
* The function `reverseBegin()` is a generator that yields elements in reverse order starting from
* the last element.
*/
*reverseBegin(): Generator<E> {
let index = this.size - 1;
while (index >= 0) {
yield this.at(index);
index--;
}
}
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*/
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* The `at` function retrieves an element at a specified position in an array-like data structure.
* @param {number} pos - The `pos` parameter represents the position of the element that you want to
* retrieve from the data structure. It is of type `number` and should be a valid index within the
* range of the data structure.
* @returns The element at the specified position in the data structure is being returned.
*/
at(pos: number): E {
rangeCheck(pos, 0, this.size - 1);
const { bucketIndex, indexInBucket } = this._getBucketAndPosition(pos);
return this._buckets[bucketIndex][indexInBucket]!;
}
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*/
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* The `setAt` function sets an element at a specific position in an array-like data structure.
* @param {number} pos - The `pos` parameter represents the position at which the element needs to be
* set. It is of type `number`.
* @param {E} element - The `element` parameter is the value that you want to set at the specified
* position in the data structure.
*/
setAt(pos: number, element: E): boolean {
rangeCheck(pos, 0, this.size - 1);
const { bucketIndex, indexInBucket } = this._getBucketAndPosition(pos);
this._buckets[bucketIndex][indexInBucket] = element;
return true;
}
/**
* Time Complexity: O(n)
* Space Complexity: O(n)
*/
/**
* Time Complexity: O(n)
* Space Complexity: O(n)
*
* The `addAt` function inserts one or more elements at a specified position in an array-like data
* structure.
* @param {number} pos - The `pos` parameter represents the position at which the element(s) should
* be inserted. It is of type `number`.
* @param {E} element - The `element` parameter represents the element that you want to insert into
* the array at the specified position.
* @param [num=1] - The `num` parameter represents the number of times the `element` should be
* inserted at the specified position (`pos`). By default, it is set to 1, meaning that the `element`
* will be inserted once. However, you can provide a different value for `num` if you want
* @returns The size of the array after the insertion is being returned.
*/
addAt(pos: number, element: E, num = 1): boolean {
const length = this.size;
rangeCheck(pos, 0, length);
if (pos === 0) {
while (num--) this.unshift(element);
} else if (pos === this.size) {
while (num--) this.push(element);
} else {
const arr: E[] = [];
for (let i = pos; i < this.size; ++i) {
arr.push(this.at(i));
}
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;
}
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*/
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* The `cut` function updates the state of the object based on the given position and returns the
* updated size.
* @param {number} pos - The `pos` parameter represents the position at which the string should be
* cut. It is a number that indicates the index of the character where the cut should be made.
* @param {boolean} isCutSelf - If true, the original deque will not be cut, and return a new deque
* @returns The method is returning the updated size of the data structure.
*/
cut(pos: number, isCutSelf = false): Deque<E> {
if (isCutSelf) {
if (pos < 0) {
this.clear();
return this;
}
const { bucketIndex, indexInBucket } = this._getBucketAndPosition(pos);
this._bucketLast = bucketIndex;
this._lastInBucket = indexInBucket;
this._size = pos + 1;
return this;
} else {
const newDeque = new Deque<E>([], { bucketSize: this._bucketSize });
for (let i = 0; i <= pos; i++) {
newDeque.push(this.at(i));
}
return newDeque;
}
}
/**
* Time Complexity: O(1)
* Space Complexity: O(1) or O(n)
*/
/**
* Time Complexity: O(1)
* Space Complexity: O(1) or O(n)
*
* The `cutRest` function cuts the elements from a specified position in a deque and returns a new
* deque with the cut elements.
* @param {number} pos - The `pos` parameter represents the position from which to cut the Deque. It
* is a number that indicates the index of the element in the Deque where the cut should start.
* @param [isCutSelf=false] - isCutSelf is a boolean parameter that determines whether the original
* Deque should be modified or a new Deque should be created. If isCutSelf is true, the original
* Deque will be modified by cutting off elements starting from the specified position. If isCutSelf
* is false, a new De
* @returns The function `cutRest` returns either the modified original deque (`this`) or a new deque
* (`newDeque`) depending on the value of the `isCutSelf` parameter.
*/
cutRest(pos: number, isCutSelf = false): Deque<E> {
if (isCutSelf) {
if (pos < 0) {
this.clear();
return this;
}
const { bucketIndex, indexInBucket } = this._getBucketAndPosition(pos);
this._bucketFirst = bucketIndex;
this._firstInBucket = indexInBucket;
this._size = this._size - pos;
return this;
} else {
const newDeque = new Deque<E>([], { bucketSize: this._bucketSize });
for (let i = pos; i < this.size; i++) {
newDeque.push(this.at(i));
}
return newDeque;
}
}
/**
* Time Complexity: O(n)
* Space Complexity: O(1) or O(n)
*/
/**
* Time Complexity: O(n)
* Space Complexity: O(1) or O(n)
*
* The `deleteAt` function removes an element at a specified position in an array-like data
* structure.
* @param {number} pos - The `pos` parameter in the `deleteAt` function represents the position at
* which an element needs to be deleted from the data structure. It is of type `number` and indicates
* the index of the element to be deleted.
* @returns The size of the data structure after the deletion operation is performed.
*/
deleteAt(pos: number): boolean {
rangeCheck(pos, 0, this.size - 1);
if (pos === 0) this.shift();
else if (pos === this.size - 1) this.pop();
else {
const length = this.size - 1;
let { bucketIndex: curBucket, indexInBucket: curPointer } = this._getBucketAndPosition(pos);
for (let i = pos; i < length; ++i) {
const { bucketIndex: nextBucket, indexInBucket: nextPointer } = this._getBucketAndPosition(pos + 1);
this._buckets[curBucket][curPointer] = this._buckets[nextBucket][nextPointer];
curBucket = nextBucket;
curPointer = nextPointer;
}
this.pop();
}
return true;
}
/**
* Time Complexity: O(n)
* Space Complexity: O(1)
*/
/**
* Time Complexity: O(n)
* Space Complexity: O(1)
*
* The `delete` function removes all occurrences of a specified element from an array-like data
* structure.
* @param {E} element - The `element` parameter represents the element that you want to delete from
* the data structure.
* @returns The size of the data structure after the element has been deleted.
*/
delete(element: E): boolean {
const size = this.size;
if (size === 0) return false;
let i = 0;
let index = 0;
while (i < size) {
const oldElement = this.at(i);
if (oldElement !== element) {
this.setAt(index, oldElement!);
index += 1;
}
i += 1;
}
this.cut(index - 1, true);
return true;
}
/**
* Time Complexity: O(n)
* Space Complexity: O(1)
*/
/**
* Time Complexity: O(n)
* Space Complexity: O(1)
*
* The reverse() function reverses the order of the buckets and the elements within each bucket in a
* data structure.
* @returns The reverse() method is returning the object itself (this) after performing the reverse
* operation on the buckets and updating the relevant properties.
*/
reverse(): this {
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;
}
/**
* Time Complexity: O(n)
* Space Complexity: O(1)
*/
/**
* Time Complexity: O(n)
* Space Complexity: O(1)
*
* The `unique()` function removes duplicate elements from an array-like data structure and returns
* the number of unique elements.
* @returns The size of the modified array is being returned.
*/
unique(): this {
if (this.size <= 1) {
return this;
}
let index = 1;
let prev = this.at(0);
for (let i = 1; i < this.size; ++i) {
const cur = this.at(i);
if (cur !== prev) {
prev = cur;
this.setAt(index++, cur);
}
}
this.cut(index - 1, true);
return this;
}
/**
* Time Complexity: O(n log n)
* Space Complexity: O(n)
*/
/**
* Time Complexity: O(n log n)
* Space Complexity: O(n)
*
* The `sort` function sorts the elements in a data structure using a provided comparator function.
* @param [comparator] - The `comparator` parameter is a function that takes in two elements `x` and
* `y` of type `E` and returns a number. The comparator function is used to determine the order of
* the elements in the sorted array.
* @returns Deque<E>
*/
sort(comparator?: (x: E, y: E) => number): this {
const arr: E[] = [];
for (let i = 0; i < this.size; ++i) {
arr.push(this.at(i));
}
arr.sort(comparator);
for (let i = 0; i < this.size; ++i) {
this.setAt(i, arr[i]);
}
return this;
}
/**
* Time Complexity: O(n)
* Space Complexity: O(n)
*/
/**
* Time Complexity: O(n)
* Space Complexity: O(n)
*
* The `shrinkToFit` function reorganizes the elements in an array-like data structure to minimize
* memory usage.
* @returns Nothing is being returned. The function is using the `return` statement to exit early if
* `this.size` is 0, but it does not return any value.
*/
shrinkToFit(): void {
if (this.size === 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;
}
/**
* Time Complexity: O(n)
* Space Complexity: O(1)
*/
/**
* Time Complexity: O(n)
* Space Complexity: O(1)
*
* The function "indexOf" returns the index of the first occurrence of a given element in an array,
* or -1 if the element is not found.
* @param {E} element - The "element" parameter represents the element that you want to find the
* index of in the data structure.
* @returns The indexOf function returns the index of the first occurrence of the specified element
* in the data structure. If the element is not found, it returns -1.
*/
indexOf(element: E): number {
for (let i = 0; i < this.size; ++i) {
if (this.at(i) === element) {
return i;
}
}
return -1;
}
/**
* Time Complexity: O(n)
* Space Complexity: O(n)
*/
/**
* Time Complexity: O(n)
* Space Complexity: O(n)
*
* The `toArray` function converts the elements of a data structure into an array.
* @returns The `toArray()` method is returning an array of elements of type `E`.
*/
toArray(): E[] {
return [...this];
}
/**
* Time Complexity: O(n)
* Space Complexity: O(n)
*/
/**
* Time Complexity: O(n)
* Space Complexity: O(n)
*
* The `clone()` function returns a new instance of the `Deque` class with the same elements and
* bucket size as the original instance.
* @returns The `clone()` method is returning a new instance of the `Deque` class with the same
* elements as the original deque (`this`) and the same bucket size.
*/
clone(): Deque<E, R> {
return new Deque<E, R>(this, { bucketSize: this.bucketSize, toElementFn: this.toElementFn });
}
/**
* Time Complexity: O(n)
* Space Complexity: O(n)
*/
/**
* Time Complexity: O(n)
* Space Complexity: O(n)
*
* The `filter` function creates a new deque containing elements from the original deque that satisfy
* a given predicate function.
* @param predicate - The `predicate` parameter is a callback function that takes three arguments:
* the current element being iterated over, the index of the current element, and the deque itself.
* It should return a boolean value indicating whether the element should be included in the filtered
* deque or not.
* @param {any} [thisArg] - The `thisArg` parameter is an optional argument that specifies the value
* to be used as `this` when executing the `predicate` function. If `thisArg` is provided, it will be
* passed as the `this` value to the `predicate` function. If `thisArg` is
* @returns The `filter` method is returning a new `Deque` object that contains the elements that
* satisfy the given predicate function.
*/
filter(predicate: ElementCallback<E, R, boolean, Deque<E, R>>, thisArg?: any): Deque<E, R> {
const newDeque = new Deque<E, R>([], { bucketSize: this._bucketSize, toElementFn: this.toElementFn });
let index = 0;
for (const el of this) {
if (predicate.call(thisArg, el, index, this)) {
newDeque.push(el);
}
index++;
}
return newDeque;
}
/**
* Time Complexity: O(n)
* Space Complexity: O(n)
*/
/**
* The `map` function takes a callback function and applies it to each element in the deque,
* returning a new deque with the results.
* @param callback - The callback parameter is a function that will be called for each element in the
* deque. It takes three arguments: the current element, the index of the element, and the deque
* itself. It should return a value of type EM.
* @param [toElementFn] - The `toElementFn` parameter is an optional function that can be used to
* transform the raw element (`RM`) into a new element (`EM`) before adding it to the new deque. If
* provided, this function will be called for each raw element in the original deque.
* @param {any} [thisArg] - The `thisArg` parameter is an optional argument that allows you to
* specify the value of `this` within the callback function. It is used to set the context or scope
* in which the callback function will be executed. If `thisArg` is provided, it will be used as the
* value of
* @returns a new Deque object with elements of type EM and raw elements of type RM.
*/
map<EM, RM>(
callback: ElementCallback<E, R, EM, Deque<E, R>>,
toElementFn?: (rawElement: RM) => EM,
thisArg?: any
): Deque<EM, RM> {
const newDeque = new Deque<EM, RM>([], { bucketSize: this._bucketSize, toElementFn });
let index = 0;
for (const el of this) {
newDeque.push(callback.call(thisArg, el, index, this));
index++;
}
return newDeque;
}
/**
* Time Complexity: O(n)
* Space Complexity: O(1)
*/
/**
* Time Complexity: O(n)
* Space Complexity: O(1)
*
* The above function is an implementation of the iterator protocol in TypeScript, allowing the
* object to be iterated over using a for...of loop.
*/
protected *_getIterator(): IterableIterator<E> {
for (let i = 0; i < this.size; ++i) {
yield this.at(i);
}
}
/**
* Time Complexity: O(n)
* Space Complexity: O(n)
*/
/**
* Time Complexity: O(n)
* Space Complexity: O(n)
*
* The `_reallocate` function reallocates the buckets in an array, adding new buckets if needed.
* @param {number} [needBucketNum] - The `needBucketNum` parameter is an optional number that
* specifies the number of new buckets needed. If not provided, it will default to half of the
* current bucket count (`this._bucketCount >> 1`) or 1 if the current bucket count is less than 2.
*/
protected _reallocate(needBucketNum?: number) {
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;
}
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*/
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* The function calculates the bucket index and index within the bucket based on the given position.
* @param {number} pos - The `pos` parameter represents the position within the data structure. It is
* a number that indicates the index or position of an element within the structure.
* @returns an object with two properties: "bucketIndex" and "indexInBucket".
*/
protected _getBucketAndPosition(pos: number) {
let bucketIndex: number;
let indexInBucket: number;
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 };
}
}