import { isIterable, isArrayLike } from '../core/traits'; import { slice } from '../core/array'; export class CircularBuffer { protected _data: T[]; protected _size: number; protected _first: number; protected _last: number; constructor(capacity: number); constructor(data: ArrayLike | Iterable); constructor(); constructor(arg1?: any) { if (typeof arg1 === 'number') { this._data = new Array(arg1); this._size = 0; } else if (isIterable(arg1)) { this._data = [...arg1]; this._size = this._data.length; } else if (isArrayLike(arg1)) { this._data = slice(arg1); this._size = this._data.length; } else { this._data = []; this._size = 0; } this._first = 0; this._last = this._data.length !== 0 ? this._data.length - 1 : 0; } get size() { return this._size; } get capacity() { return this._data.length; } isFull() { return this._size === this._data.length; } isEmpty() { return this._size === 0; } overflow() { this.shift(); } underflow() { this.pop(); } rotateLeft(offset = 1) { const data = this._data; const length = data.length; if (offset < 0) { this.rotateRight(-offset); return; } offset %= length; if (offset === 0) return; reverse(data, 0, offset - 1); reverse(data, offset, length - 1); reverse(data, 0, length - 1); this._first -= offset; if (this._first < 0) this._first += length; this._last -= offset; if (this._last < 0) this._last += length; } rotateRight(offset = 1) { const data = this._data; const length = data.length; if (offset < 0) { this.rotateLeft(-offset); return; } offset %= length; if (offset === 0) return; reverse(data, 0, length - 1); reverse(data, 0, offset - 1); reverse(data, offset, length - 1); this._first += offset; if (this._first >= length) this._first -= length; this._last += offset; if (this._last >= length) this._last -= length; } normalize() { this.rotateLeft(this._first); } resize(size: number) { if (size === this._data.length) return; if (size === 0) { this._first = this._last = this._size = this._data.length = 0; return; } this.normalize(); this._data.length = size; if (this._size === 0) { this._last = size - 1; } else if (this._size > size) { this._size = size; this._last = size - 1; } } first() { if (this._size === 0) return; return this._data[this._first]; } last() { if (this._size === 0) return; return this._data[this._last]; } get(index: number) { if (index < 0 || index >= this._size) return; index += this._first; if (index >= this._data.length) index -= this._data.length; return this._data[index]; } set(index: number, value: T) { if (index < 0 || index >= this._size) return; index += this._first; if (index >= this._data.length) index -= this._data.length; this._data[index] = value; } push(...values: T[]) { for (let value of values) { if (this._size === this._data.length) { this.overflow(); if (this._size === this._data.length) return this._size; } if (++this._last === this._data.length) this._last = 0; this._size++; this._data[this._last] = value; } return this._size; } pop() { if (this._size === 0) return; const ret = this._data[this._last]; if (--this._last === -1) this._last += this._data.length; this._size--; return ret; } unshift(...values: T[]) { for (let value of values) { if (this._size === this._data.length) { this.underflow(); if (this._size === this._data.length) return this._size; } if (--this._first === -1) this._first += this._data.length; this._size++; this._data[this._first] = value; } return this._size; } shift() { if (this._size === 0) return; const ret = this._data[this._first]; if (++this._first === this._data.length) this._first = 0; this._size--; return ret; } slice(start?: number, end?: number) { let size = this._size; start = start === undefined ? 0 : start; start = start < 0 ? Math.max(0, size + start) : start; end = end === undefined ? size : end; end = end < 0 ? size + end : Math.min(end, size); size = end - start; if (size <= 0) return []; const data = this._data; const length = data.length; const offset = this._first; start += offset; if (start >= length) start -= length; end += offset; if (end >= length) end -= length; return start < end ? data.slice(start, end) : data.slice(start, length).concat(data.slice(0, end)); } forEach(callbackfn: (value: T, index: number, buffer: CircularBuffer) => void, thisArg?: any) { for (let index = this._first, size = this._size, vIndex = 0; size !== 0; --size, ++vIndex) { callbackfn.call(thisArg, this._data[index], vIndex, this); if (++index === this._data.length) index = 0; } } *[Symbol.iterator]() { const data = this._data; for (let index = this._first, size = this._size; size !== 0; --size) { yield data[index]; if (++index === this._data.length) index = 0; } } } //! \see https://leetcode.com/articles/rotate-array/#approach-4-using-reverse-accepted function reverse(arr: T[], first: number, last: number) { while (first < last) { const tmp = arr[first]; arr[first] = arr[last]; arr[last] = tmp; ++first; --last; } }