lifecycle-utils
Version:
A set of general utilities for the lifecycle of a JS/TS project/library
258 lines • 7.64 kB
JavaScript
/**
* An efficient queue implementation that allows you to enqueue and dequeue items in `O(1)` time complexity.
*
* ```typescript
* import {Queue} from "lifecycle-utils";
*
* const queue = new Queue([1, 2, 3]);
*
* queue.push(4);
* console.log(queue.shift()); // 1
*
* console.log(queue.first); // 2
* console.log(queue.last); // 4
* console.log(queue.length); // 3
* console.log([...queue]); // [2, 3, 4]
* ```
*/
export class Queue {
/** @internal */ _items;
/** @internal */ _head = 0;
/** @internal */ _compactAt;
/**
* Creates a new queue.
*
* **Time complexity:** `O(n)` with initial values, or `O(1)` without them.
*/
constructor(values, options) {
this._compactAt = options?.compactAt ?? 4096;
if (values instanceof Queue)
this._items = values._items.slice(values._head);
else
this._items = values == null
? []
: Array.from(values);
}
/**
* The number of values in the queue.
*
* **Time complexity:** `O(1)`.
*/
get length() {
return this._items.length - this._head;
}
/**
* Whether the queue is empty.
*
* **Time complexity:** `O(1)`.
*/
get isEmpty() {
return this._head === this._items.length;
}
/**
* The first (next) value in the queue, or `undefined` when the queue is empty.
*
* **Time complexity:** `O(1)`.
*/
get first() {
if (this._head === this._items.length)
return undefined;
return this._items[this._head];
}
/**
* The last value in the queue, or `undefined` when the queue is empty.
*
* **Time complexity:** `O(1)`.
*/
get last() {
if (this._head === this._items.length)
return undefined;
return this._items[this._items.length - 1];
}
/**
* Adds a value to the end of the queue.
*
* **Time complexity:** `O(1)` amortized.
*/
push(item) {
this._items.push(item);
}
/**
* Removes and returns the first (next) value in the queue.
*
* Returns `undefined` when the queue is empty.
*
* **Time complexity:** `O(1)` amortized, with occasional `O(n)` compaction.
*/
shift() {
if (this._head === this._items.length)
return undefined;
const item = this._items[this._head];
this._items[this._head] = null;
this._head++;
if (this._head >= this._compactAt && this._head * 2 >= this._items.length) {
this._items = this._items.slice(this._head);
this._head = 0;
}
return item;
}
/**
* Returns the value at the given index without removing it.
*
* Negative indexes count backwards from the end of the queue.
*
* **Time complexity:** `O(1)`.
*/
at(index) {
index = Math.trunc(index) || 0;
if (index < 0)
index += this.length;
if (index < 0 || index >= this.length)
return undefined;
return this._items[this._head + index];
}
/**
* Deletes values from the queue starting at `start` and ending before `end`.
*
* When `end` is omitted, only the value at `start` is deleted.
* Negative indexes count backwards from the end of the queue.
*
* Returns the number of deleted values.
*
* **Time complexity:** `O(k)` when deleting from either end, where `k` is the number of deleted values,
* and `O(n)` when deleting from the middle.
*/
delete(start, end) {
const length = this.length;
start = Math.trunc(start) || 0;
if (start < 0)
start = Math.max(length + start, 0);
else
start = Math.min(start, length);
if (end == null)
end = Math.min(start + 1, length);
else {
end = Math.trunc(end) || 0;
if (end < 0)
end = Math.max(length + end, 0);
else
end = Math.min(end, length);
}
if (end <= start)
return 0;
const deleteCount = end - start;
if (deleteCount === length) {
this._items = [];
this._head = 0;
return deleteCount;
}
if (start === 0) {
this._items.fill(null, this._head, this._head + deleteCount);
this._head += deleteCount;
if (this._head >= this._compactAt && this._head * 2 >= this._items.length) {
this._items = this._items.slice(this._head);
this._head = 0;
}
return deleteCount;
}
if (end === length) {
this._items.length = this._head + start;
return deleteCount;
}
this._items.splice(this._head + start, deleteCount);
return deleteCount;
}
/**
* Returns the index of the first occurrence of a value in the queue, or `-1` when it is not found.
*
* **Time complexity:** `O(n)`.
*/
indexOf(item, fromIndex = 0) {
const length = this.length;
if (length === 0)
return -1;
fromIndex = Math.trunc(fromIndex) || 0;
if (fromIndex >= length)
return -1;
if (fromIndex < 0)
fromIndex = Math.max(length + fromIndex, 0);
const index = this._items.indexOf(item, this._head + fromIndex);
if (index < 0)
return -1;
return index - this._head;
}
/**
* Returns the index of the last occurrence of a value in the queue, or `-1` when it is not found.
*
* **Time complexity:** `O(n)`.
*/
lastIndexOf(item, fromIndex) {
const length = this.length;
if (length === 0)
return -1;
if (fromIndex == null)
fromIndex = length - 1;
else
fromIndex = Math.trunc(fromIndex) || 0;
if (fromIndex >= 0)
fromIndex = Math.min(fromIndex, length - 1);
else
fromIndex += length;
if (fromIndex < 0)
return -1;
const index = this._items.lastIndexOf(item, this._head + fromIndex);
if (index < this._head)
return -1;
return index - this._head;
}
/**
* Returns an iterator over index-value pairs in the queue.
*
* **Time complexity:** `O(n)` for a full iteration and `O(1)` per value.
*/
*entries() {
let index = 0;
while (index < this.length) {
yield [index, this._items[this._head + index]];
index++;
}
}
/**
* Returns an iterator over the values in the queue from first to last.
*
* **Time complexity:** `O(n)` for a full iteration and `O(1)` per value.
*/
*values() {
let index = 0;
while (index < this.length) {
yield this._items[this._head + index];
index++;
}
}
/**
* Returns the queue values as a new array.
*
* **Time complexity:** `O(n)`.
*/
toArray() {
return this._items.slice(this._head);
}
/**
* Removes all values from the queue.
*
* **Time complexity:** `O(1)`.
*/
clear() {
this._items = [];
this._head = 0;
}
/**
* Returns an iterator over the values in the queue from first to last.
*
* **Time complexity:** `O(1)` to create the iterator, and `O(n)` for a full iteration.
*/
[Symbol.iterator]() {
return this.values();
}
}
//# sourceMappingURL=Queue.js.map