spica
Version:
Supervisor, Coroutine, Channel, select, AtomicPromise, Cancellation, Cache, List, Queue, Stack, and some utils.
203 lines (198 loc) • 6.16 kB
text/typescript
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;
}