fastds
Version:
Fast, Zero-Dependency, TypeScript-based data structures for high-performance applications.
585 lines (504 loc) • 15 kB
text/typescript
const DEFAULT_CAPACITY = 8;
export interface FilterFunction<T> {
(value: T, index: number): boolean;
}
export class RingBuffer<T> {
#buffer: Array<T | undefined>;
#head = 0;
#tail = 0;
#mask: number;
#length = 0;
static from<T>(values: T[]): RingBuffer<T> {
const n = values.length;
const ring = new RingBuffer<T>(n + 1);
for (let i = 0; i < n; i++) {
ring.#buffer[i] = values[i];
}
ring.#head = 0;
ring.#tail = n;
ring.#length = n;
return ring;
}
readonly [Symbol.toStringTag] = 'RingBuffer';
constructor(capacity: number = DEFAULT_CAPACITY) {
const size = Math.max(1 << (32 - Math.clz32(capacity - 1)), DEFAULT_CAPACITY);
this.#buffer = new Array<T>(size);
this.#mask = size - 1;
}
get capacity() {
return this.#mask + 1;
}
get length() {
return this.#length;
}
isEmpty(): boolean {
return this.#tail === this.#head;
}
isWrapped(): boolean {
return this.#head > this.#tail;
}
getHeadOffset(index: number): number {
return (this.#head + index) & this.#mask;
}
getTailOffset(index: number): number {
return (this.#tail + index) & this.#mask;
}
resize(capacity: number): boolean {
const buffer = this.#buffer;
const bufferLength = buffer.length;
if (bufferLength > capacity && bufferLength >> 1 < capacity) {
return false;
}
const size = Math.max(1 << (32 - Math.clz32(capacity - 1)), DEFAULT_CAPACITY);
const length = this.#length;
if (size < length) {
return false;
}
const head = this.#head;
const prevMask = this.#mask;
const prevTail = this.#tail;
const nextMask = size - 1;
const nextTail = (head + length) & nextMask;
const wrapIndex = size > bufferLength ? (prevTail < head ? bufferLength - head : length) : nextTail < head ? size - head : length;
for (let i = length - 1; i >= wrapIndex; i--) {
const read = (head + i) & prevMask;
const write = (head + i) & nextMask;
buffer[write] = buffer[read];
if (read !== write) {
buffer[read] = undefined;
}
}
this.#buffer.length = size;
this.#tail = nextTail;
this.#mask = nextMask;
return true;
}
grow(capacity: number = this.#mask + 1): void {
const buffer = this.#buffer;
const bufferLength = buffer.length;
if (bufferLength >= capacity + 1) {
return;
}
const size = 1 << (32 - Math.clz32(capacity));
this.#buffer.length = size;
const oldTail = this.#tail;
if (oldTail < this.#head) {
for (let i = 0; i < oldTail; i++) {
buffer[bufferLength + i] = buffer[i];
}
this.#tail = bufferLength + oldTail;
}
this.#mask = size - 1;
}
allocate(index: number, count: number): boolean {
const prevLength = this.#length;
if (index < 0 || count <= 0) {
return false;
}
index = Math.min(index, prevLength);
const buffer = this.#buffer;
const head = this.#head;
const tail = this.#tail;
const prevMask = this.#mask;
const nextLength = count + prevLength;
const wrapIndex = buffer.length - head;
const isWrapped = head > tail;
if (nextLength >= buffer.length) {
const size = 1 << (32 - Math.clz32(nextLength));
buffer.length = size;
this.#mask = size - 1;
}
const mask = this.#mask;
const isResized = prevMask !== mask;
const leftMoveCount = index;
const rightMoveCount = prevLength - index;
if (leftMoveCount < rightMoveCount) {
if (isResized && isWrapped) {
for (let i = 0; i < tail; i++) {
const read = i;
const write = (head + wrapIndex + i) & mask;
buffer[write] = buffer[read];
buffer[read] = undefined;
}
}
const writeBase = (head - count) & mask;
for (let i = 0; i < leftMoveCount; i++) {
const read = (head + i) & mask;
const write = (writeBase + i) & mask;
buffer[write] = buffer[read];
buffer[read] = undefined;
}
this.#head = writeBase;
} else {
if (isResized && isWrapped) {
for (let i = 0; i < tail; i++) {
const read = i;
const write = (head + wrapIndex + i) & mask;
buffer[write] = buffer[read];
buffer[read] = undefined;
}
}
for (let i = rightMoveCount - 1; i >= 0; i--) {
const read = (head + index + i) & mask;
const write = (head + index + count + i) & mask;
buffer[write] = buffer[read];
buffer[read] = undefined;
}
this.#tail = (head + nextLength) & mask;
}
this.#length = nextLength;
return true;
}
deallocate(index: number, count: number): boolean {
const prevLength = this.#length;
if (index < 0 || index >= prevLength || count <= 0) {
return false;
}
const actualCount = Math.min(count, prevLength - index);
const nextLength = prevLength - actualCount;
const buffer = this.#buffer;
const head = this.#head;
const tail = this.#tail;
const mask = this.#mask;
if (index === 0) {
for (let i = 0; i < actualCount; i++) {
buffer[(head + i) & mask] = undefined;
}
this.#head = (head + actualCount) & mask;
this.#length = nextLength;
return true;
}
if (index + actualCount === prevLength) {
for (let i = 0; i < actualCount; i++) {
buffer[(tail - i - 1) & mask] = undefined;
}
this.#tail = (tail - actualCount) & mask;
this.#length = nextLength;
return true;
}
const leftMoveCount = index;
const rightMoveCount = prevLength - index - actualCount;
if (leftMoveCount < rightMoveCount) {
for (let i = leftMoveCount - 1; i >= 0; i--) {
const read = (head + i) & mask;
const write = (head + i + actualCount) & mask;
buffer[write] = buffer[read];
}
for (let i = 0; i < actualCount; i++) {
buffer[(head + i) & mask] = undefined;
}
this.#head = (head + actualCount) & mask;
} else {
for (let i = 0; i < rightMoveCount; i++) {
const read = (head + index + actualCount + i) & mask;
const write = (head + index + i) & mask;
buffer[write] = buffer[read];
}
this.#tail = (tail - actualCount) & mask;
for (let i = 0; i < actualCount; i++) {
buffer[(tail + i) & mask] = undefined;
}
}
this.#length = nextLength;
return true;
}
indexOf(value: T, index: number = 0): number {
const length = this.#length;
const buffer = this.#buffer;
const head = this.#head;
const tail = this.#tail;
if (head < tail) {
const offset = head + index;
const result = buffer.indexOf(value, offset);
if (result !== -1 && result < tail) {
return result - head;
}
return -1;
}
const capacity = buffer.length;
const firstSegmentLength = capacity - head;
if (index < firstSegmentLength) {
const result = buffer.indexOf(value, head + index);
if (result !== -1) {
return result - head;
}
index = firstSegmentLength;
}
if (index < length) {
const result = buffer.indexOf(value, 0);
if (result !== -1 && result < tail) {
return firstSegmentLength + result;
}
}
return -1;
}
unwrap(): boolean {
if (this.isEmpty()) {
return false;
}
const length = this.#length;
const buffer = this.#buffer;
const prevHead = this.#head;
const prevTail = this.#tail;
if (prevHead > prevTail) {
const bufferLength = buffer.length;
buffer.length = bufferLength + prevTail;
for (let i = 0; i < prevTail; i++) {
buffer[bufferLength + i] = buffer[i];
buffer[i] = undefined;
}
}
for (let i = 0; i < length; i++) {
buffer[i] = buffer[prevHead + i];
}
buffer.length = this.#mask + 1;
this.#head = 0;
this.#tail = length;
this.#length = length;
return true;
}
compact(filter: FilterFunction<T>): boolean {
if (this.isEmpty()) {
return false;
}
const length = this.#length;
const buffer = this.#buffer;
const head = this.#head;
const mask = this.#mask;
let bufferLength = buffer.length;
let write = 0;
for (let read = 0; read < length; read++) {
const readOffset = (head + read) & mask;
const value = buffer[readOffset]!;
if (filter(value, read)) {
if (read !== write) {
const writeOffset = (head + write) & mask;
buffer[writeOffset] = value;
}
write++;
}
}
if (write === length) {
return false;
}
if (write < bufferLength / 2) {
const size = 1 << (32 - Math.clz32(write - 1));
buffer.length = size;
bufferLength = size;
}
for (let i = write; i < bufferLength; i++) {
buffer[i] = undefined;
}
this.#head = 0;
this.#tail = write;
this.#length = write;
return true;
}
set(index: number, values: T[], insert = false) {
const length = this.#length;
if (index < 0 || index > length) {
return false;
}
const count = values.length;
if (insert) {
this.allocate(index, count);
} else {
const extra = Math.max(index + values.length - length, 0);
if (extra > 0) {
this.allocate(length, extra);
}
}
const buffer = this.#buffer;
const mask = this.#mask;
const baseWrite = this.#head + index;
for (let i = 0; i < count; i++) {
buffer[(baseWrite + i) & mask] = values[i];
}
return true;
}
setOne(index: number, value: T, insert = false) {
const length = this.#length;
if (index < 0 || index > length) {
return false;
}
if (insert) {
this.allocate(index, 1);
} else {
const extra = Math.max(index + 1 - length, 0);
if (extra > 0) {
this.allocate(length, extra);
}
}
const buffer = this.#buffer;
const mask = this.#mask;
buffer[(this.#head + index) & mask] = value;
return true;
}
slice(start: number = 0, end: number = this.#length): T[] {
const length = this.#length;
const buffer = this.#buffer;
const head = this.#head;
const tail = this.#tail;
const mask = this.#mask;
const actualStart = start < 0 ? Math.max(length + start, 0) : Math.min(start, length);
const actualEnd = end < 0 ? Math.max(length + end, 0) : Math.min(end, length);
if (head <= tail) {
return this.#buffer.slice((head + actualStart) & mask, (head + actualEnd) & mask) as T[];
}
const size = Math.max(actualEnd - actualStart, 0);
const result = new Array<T>(size);
for (let i = 0; i < size; i++) {
result[i] = buffer[(head + actualStart + i) & mask]!;
}
return result;
}
removeOne(index: number): number {
const length = this.#length;
if (index < 0 || index >= length) {
return -1;
}
const buffer = this.#buffer;
const mask = this.#mask;
const head = this.#head;
const leftMoveCount = index;
const rightMoveCount = length - index;
if (leftMoveCount < rightMoveCount) {
for (let i = index; i > 0; i--) {
buffer[(head + i) & mask] = buffer[(head + i - 1) & mask];
}
buffer[head] = undefined;
this.#head = (head + 1) & mask;
} else {
for (let i = index; i < length - 1; i++) {
buffer[(head + i) & mask] = buffer[(head + i + 1) & mask];
}
const tail = (head + length - 1) & mask;
buffer[tail] = undefined;
this.#tail = tail;
}
this.#length = length - 1;
return index;
}
removeFirst(value: T, index: number = 0): number {
const foundIndex = this.indexOf(value, index);
if (foundIndex === -1) {
return -1;
}
return this.removeOne(foundIndex);
}
remove(index: number, count: number): T[] {
const result = this.slice(index, index + count);
this.deallocate(index, count);
return result;
}
push(value: T) {
const nextTail = (this.#tail + 1) & this.#mask;
if (nextTail === this.#head) {
this.grow(this.#mask + 2);
this.#buffer[this.#tail] = value;
this.#tail = (this.#tail + 1) & this.#mask;
} else {
this.#buffer[this.#tail] = value;
this.#tail = nextTail;
}
this.#length++;
return this;
}
unshift(value: T): this {
const newHead = (this.#head - 1) & this.#mask;
if (newHead === this.#tail) {
this.grow(this.#mask + 2);
this.#head = (this.#head - 1) & this.#mask;
} else {
this.#head = newHead;
}
this.#buffer[this.#head] = value;
this.#length++;
return this;
}
shift(): T | undefined {
if (this.#head === this.#tail) {
return undefined;
}
const value = this.#buffer[this.#head];
this.#buffer[this.#head] = undefined;
this.#head = (this.#head + 1) & this.#mask;
this.#length--;
return value;
}
pop(): T | undefined {
if (this.#head === this.#tail) {
return undefined;
}
this.#tail = (this.#tail - 1) & this.#mask;
const value = this.#buffer[this.#tail];
this.#buffer[this.#tail] = undefined;
this.#length--;
return value;
}
peekAt(index: number): T | undefined {
if (index < 0 || index >= this.#length) {
return undefined;
}
const offset = this.getHeadOffset(index);
return this.#buffer[offset];
}
peekFirst(): T | undefined {
return this.#buffer[this.#head];
}
peekLast(): T | undefined {
const offset = this.getTailOffset(-1);
return this.#buffer[offset];
}
has(value: T): boolean {
return this.indexOf(value) !== -1;
}
clear(): this {
this.#buffer.length = 0;
this.#buffer.length = DEFAULT_CAPACITY;
this.#head = 0;
this.#tail = 0;
this.#length = 0;
this.#mask = DEFAULT_CAPACITY - 1;
return this;
}
toArray(): T[] {
return this.slice();
}
drain(): IteratorObject<T, void, unknown> {
return Iterator.from({
[Symbol.iterator]: () => {
return {
next: (): IteratorResult<T> => {
if (this.#length === 0) {
return { done: true, value: undefined };
}
const value = this.shift()!;
return { done: false, value };
},
};
},
});
}
iter(): IteratorObject<T, void, unknown> {
return Iterator.from(this[Symbol.iterator]());
}
[Symbol.iterator](): Iterator<T, void, unknown> {
const buffer = this.#buffer;
const mask = this.#mask;
const length = this.#length;
let count = 0;
let idx = this.#head;
return {
next: (): IteratorResult<T> => {
if (count >= length) {
return { done: true, value: undefined };
}
const value = buffer[idx]!;
idx = (idx + 1) & mask;
count++;
return { done: false, value };
},
};
}
}