UNPKG

spica

Version:

Supervisor, Coroutine, Channel, select, AtomicPromise, Cancellation, Cache, List, Queue, Stack, and some utils.

203 lines (198 loc) 6.16 kB
import { max, min } from './alias'; import { indexOf, splice } from './array'; type empty = typeof empty; const empty = Symbol('empty'); const unempty = <T>(value: T | empty): T | undefined => value === empty ? undefined : value; const space = Object.freeze(Array<empty>(100).fill(empty)); let size = 16; assert([size = 0]); export class Ring<T> { private array: (T | empty)[] = Array(size); private head = 0; private tail = 0; private $length = 0; public get length(): number { return this.$length; } public at(index: number): T | undefined { // Inline the code for optimization. const array = this.array; if (index >= 0) { if (index >= this.$length) return; return unempty(array[(this.head - 1 + index) % array.length]); } else { if (-index > this.$length) return; return this.tail + index >= 0 ? unempty(array[this.tail + index]) : unempty(array[array.length + this.tail + index]); } } public set(index: number, value: T, replacer?: (oldValue: T, newValue: T) => T): T { const array = this.array; if (index >= 0) { if (index >= this.$length) throw new RangeError('Invalid index'); index = (this.head - 1 + index) % array.length; } else { if (-index > this.$length) throw new RangeError('Invalid index'); index = this.tail + index >= 0 ? this.tail + index : array.length + this.tail + index; } const val = unempty(array[index])!; array[index] = replacer ? replacer(val, value) : value; return val; } public push(value: T): void { const array = this.array; let { head, tail } = this; tail = this.tail = next(head, tail, array.length); head = this.head ||= tail; if (head === tail && this.$length !== 0) { splice(array, tail - 1, 0, ...space); head = this.head += space.length; } array[tail - 1] = value; ++this.$length; } public unshift(value: T): void { const array = this.array; let { head, tail } = this; head = this.head = prev(head, tail, array.length); tail = this.tail ||= head; if (head === tail && this.$length !== 0) { splice(array, head, 0, ...space); head = this.head += space.length; } array[head - 1] = value; ++this.$length; } public pop(): T | undefined { if (this.$length === 0) return; const array = this.array; const i = this.tail - 1; const value = unempty(array[i]); array[i] = empty; --this.$length === 0 ? this.head = this.tail = 0 : this.tail = this.tail === 1 ? array.length : this.tail - 1; return value; } public shift(): T | undefined { if (this.$length === 0) return; const array = this.array; const i = this.head - 1; const value = unempty(array[i]); array[i] = empty; --this.$length === 0 ? this.head = this.tail = 0 : this.head = this.head === array.length ? 1 : this.head + 1; return value; } private excess = 0; public splice(index: number, count: number, ...values: T[]): T[] { const array = this.array; if (this.excess > 100 && array.length - this.$length > 200) { splice(array, 0, 100 - splice(array, this.tail, 100).length); this.excess -= 100; } else if (-this.excess > array.length * 2) { this.excess = array.length; } index = index < 0 ? max(0, this.$length + index) : index <= this.$length ? index : this.$length; count = min(max(count, 0), this.$length - index); if (values.length === 0) { if (count === 0) return []; switch (index) { case 0: if (count === 1) return [this.shift()!]; break; case this.$length - 1: if (count === 1) return [this.pop()!]; break; case this.$length: return []; } } index = (this.head || 1) - 1 + index; index = index > array.length ? index % array.length : index; this.excess += values.length - count; this.$length += values.length - count; // |--H>*>T--| if (this.head <= this.tail) { this.tail += values.length - count; return splice(array, index, count, ...values) as T[]; } // |*>T---H>>| if (index < this.tail) { this.head += values.length - count; this.tail += values.length - count; return splice(array, index, count, ...values) as T[]; } // |>>T---H>*| const cnt = min(count, array.length - index); const vs = splice(array, index, cnt, ...splice(values, 0, cnt)); vs.push(...splice(array, 0, count - vs.length, ...values)); return vs as T[]; } public clear(): void { this.array = Array(size); this.$length = this.head = this.tail = 0; } public includes(value: T): boolean { return this.array.includes(value); } private relational(index: number): number { if (index === -1) return -1; return index + 1 >= this.head ? index + 1 - this.head : this.array.length - this.head + index; } public indexOf(value: T): number { return this.relational(indexOf(this.array, value)); } public findIndex(f: (value: T) => unknown): number { return this.relational(this.array.findIndex(value => value !== empty && f(value))); } public find(f: (value: T) => unknown): T | undefined { return unempty(this.array.find(value => value !== empty && f(value))); } public toArray(): T[] { return this.head <= this.tail ? this.array.slice((this.head || 1) - 1, this.tail) as T[] : this.array.slice((this.head || 1) - 1).concat(this.array.slice(0, this.tail)) as T[]; } public *[Symbol.iterator](): Iterator<T, undefined, undefined> { for (let i = 0; i < this.$length; ++i) { yield this.at(i)!; } } } function next(head: number, tail: number, length: number): number { return tail === length && head !== 1 ? 1 : tail + 1; } function prev(head: number, tail: number, length: number): number { return head === 0 || head === 1 ? tail === length ? length + 1 : length : head - 1; }