'use strict'; /** * Implements a queue structure * @template T Queue values type */ export default class Queue { private _length: number; private _start: any; private _end: any; /** * Creates a queue from array * @param {Array} array Array to create the queue from * @return {Queue} Queue created from the array * @deprecated Use constructor instead */ static fromArray(array: V[]) { let queue = new Queue(); array.forEach(item => queue.push(item)); return queue; } /** * Constructs instance * @param {Iterable} [items] Initial items to add to the queue */ constructor(items?: Iterable) { for (let item of items || []) { this.push(item); } this._length = 0; } /** * Returns the queue size * @return {Number} Queue size */ get length() { return this._length; } /** * Iterates by queue items */ *[Symbol.iterator](): Iterator { let current = this._start; while (current) { yield current.value; current = current.next; } } /** * Returns the first element or undefined if the queue is empty * @return {T} The first value */ front(): T | undefined { return this._start?.value; } /** * Returns the last element or undefined if the queue is empty * @return {T} The last value */ back(): T | undefined { return this._end?.value; } /** * Pushes new elements to the end of the queue * @param {T[]} values Values to push */ push(...values: T[]) { for (let value of values) { if (this._end) { this._end.next = {value, prev: this._end}; this._end = this._end.next; } else { this._start = this._end = {value}; } this._length++; } } /** * Removes the last element from the queue * @return {T} The value removed or undefined */ pop(): T | undefined { if (this._end) { let result = this._end; this._remove(this._end); return result.value; } } /** * Inserts new elements to the front of the queue * @param {T} value Values to unshift */ unshift(value: T) { if (this._start) { this._start = {value, next: this._start}; this._start.next.prev = this._start; } else { this._start = this._end = {value}; } this._length++; } /** * Removes the first element from the queue * @return {T} The value removed or undefined */ shift(): T | undefined { if (this._start) { let result = this._start; this._remove(this._start); return result.value; } } /** * Finds an element in the queue by the predicate * @param {(value: T) => boolean} predicate Predicate to use * @return {T} Element value found or undefined */ find(predicate: (value: T) => boolean): T | undefined { let current = this._start; while (current) { if (predicate(current.value)) { return current.value; } current = current.next; } } /** * Iterates over the queue * @param {(value: T, index: Number) => any} callable Iterator callable */ forEach(callable: (value: T, index: number) => any) { let current = this._start; let index = 0; while (current) { callable(current.value, index++); current = current.next; } } /** * Removes elements iterating them and matching by predicate * @param {(value: T) => boolean|null} predicate Returns true for values to remove. If returns null, iteration stops */ remove(predicate: (value: T) => boolean | null) { let current = this._start; while (current) { let remove = predicate(current.value); if (remove === null) { break; } if (remove === true) { this._remove(current); } current = current.next; } } /** * Removes the first occurence of element matched by predicate * @param predicate returns true for value to removed */ removeOne(predicate: (value: T) => boolean) { let removed = false; this.remove(item => { if (removed) { return null; } if (predicate(item)) { removed = true; return true; } }); } /** * Creates a new queue from this one in reversed order * @return {Queue} Reversed queue */ reversed(): Queue { let result = new Queue(); let current = this._end; while (current) { result.push(current.value); current = current.prev; } return result; } /** * Creates an array from the queue * @return {Array} Array created */ toArray(): T[] { let result = []; this.forEach(value => result.push(value)); return result; } /** * Clears the queue */ clear() { delete this._start; delete this._end; this._length = 0; } private _remove(node) { if (node.prev) { node.prev.next = node.next; } else { this._start = node.next; } if (node.next) { node.next.prev = node.prev; } else { this._end = node.prev; } this._length--; } }