|
1 | | -import { OverwritingArrayIterator } from './OverwritingArrayIterator.js'; |
| 1 | +import { ConstrainedNumber, clamped, wrapped } from './numbers.js'; |
2 | 2 |
|
3 | 3 | export class OverwritingArray<T> { |
4 | 4 | private _array: T[]; |
5 | | - private _index = 0; |
6 | | - private _size = 0; |
7 | | - private _startIndex = 0; |
8 | | - constructor(public readonly capacity: number) { |
9 | | - this._array = this.createArray(); |
| 5 | + |
| 6 | + private readonly _headConstraint: ConstrainedNumber; |
| 7 | + private readonly _lengthConstraint: ConstrainedNumber; |
| 8 | + |
| 9 | + private _head = 0; |
| 10 | + private _length = 0; |
| 11 | + |
| 12 | + private get head() { |
| 13 | + return this._head; |
| 14 | + } |
| 15 | + |
| 16 | + private set head(value: number) { |
| 17 | + this._head = this._headConstraint(value); |
10 | 18 | } |
11 | | - public add(value: T): this { |
12 | | - this._array[this._index] = value; |
13 | | - this._index = this.incrementIndex(this._index); |
14 | | - this._startIndex = this.incrementStartingIndex(); |
15 | | - this._size = this.incrementSize(); |
16 | | - return this; |
| 19 | + |
| 20 | + public get length() { |
| 21 | + return this._length; |
17 | 22 | } |
18 | 23 |
|
19 | | - public clear(): void { |
20 | | - this._array = this.createArray(); |
| 24 | + public set length(value: number) { |
| 25 | + this._length = this._lengthConstraint(value); |
21 | 26 | } |
22 | 27 |
|
23 | | - public values(): IterableIterator<T> { |
24 | | - return new OverwritingArrayIterator<T>(this._array, this._startIndex, this._size); |
| 28 | + private get start() { |
| 29 | + return this._headConstraint(this.head - this.length); |
25 | 30 | } |
26 | 31 |
|
27 | | - [Symbol.iterator](): IterableIterator<T> { |
28 | | - return new OverwritingArrayIterator<T>(this._array, this._startIndex, this._size); |
| 32 | + constructor( |
| 33 | + public readonly capacity: number, |
| 34 | + items?: T[], |
| 35 | + ) { |
| 36 | + this._array = new Array(capacity); |
| 37 | + |
| 38 | + // Head must be always between 0 and capacity. |
| 39 | + // If lower than 0, it needs to go from the end |
| 40 | + // If larger than capacity, it needs to go from the start |
| 41 | + // Wrapping solves this |
| 42 | + this._headConstraint = wrapped(0, capacity); |
| 43 | + |
| 44 | + // Length must be always no less than 0 and no larger than capacity |
| 45 | + this._lengthConstraint = clamped(0, capacity); |
| 46 | + |
| 47 | + if (items) { |
| 48 | + this.push(...items); |
| 49 | + } |
29 | 50 | } |
30 | 51 |
|
31 | | - private incrementIndex(index: number) { |
32 | | - return (index + 1) % this.capacity; |
| 52 | + public add(item: T) { |
| 53 | + return this.pushOne(item); |
33 | 54 | } |
34 | 55 |
|
35 | | - private incrementStartingIndex() { |
36 | | - if (this._size !== this.capacity) { |
37 | | - return this._startIndex; |
| 56 | + public push(...items: T[]): number { |
| 57 | + for (const item of items) { |
| 58 | + this.pushOne(item); |
38 | 59 | } |
39 | | - return this.incrementIndex(this._startIndex); |
| 60 | + return this.length; |
| 61 | + } |
| 62 | + |
| 63 | + public pop(): T | undefined { |
| 64 | + this.head--; |
| 65 | + const element = this._array[this.head]; |
| 66 | + this._array[this.head] = undefined as never; |
| 67 | + this.length--; |
| 68 | + return element; |
40 | 69 | } |
41 | | - private incrementSize() { |
42 | | - return Math.min(this.capacity, this._size + 1); |
| 70 | + |
| 71 | + public shift(): T | undefined { |
| 72 | + const element = this._array[this.start]; |
| 73 | + this._array[this.start] = undefined as never; |
| 74 | + this.length--; |
| 75 | + return element; |
| 76 | + } |
| 77 | + |
| 78 | + public at(index: number): T | undefined { |
| 79 | + return this._array[this.index(index)]; |
43 | 80 | } |
44 | | - private createArray() { |
45 | | - return new Array(this.capacity); |
| 81 | + |
| 82 | + public *values(): IterableIterator<T> { |
| 83 | + for (let i = 0; i < this.length; i++) { |
| 84 | + const index = this.index(i); |
| 85 | + yield this._array[index]; |
| 86 | + } |
| 87 | + } |
| 88 | + |
| 89 | + public *keys(): IterableIterator<number> { |
| 90 | + for (let i = 0; i < this.length; i++) { |
| 91 | + yield i; |
| 92 | + } |
| 93 | + } |
| 94 | + |
| 95 | + public *entries(): IterableIterator<[number, T]> { |
| 96 | + for (let i = 0; i < this.length; i++) { |
| 97 | + const index = this.index(i); |
| 98 | + yield [i, this._array[index]]; |
| 99 | + } |
| 100 | + } |
| 101 | + |
| 102 | + public [Symbol.iterator]() { |
| 103 | + return this.values(); |
| 104 | + } |
| 105 | + |
| 106 | + private pushOne(item: T) { |
| 107 | + this._array[this.head] = item; |
| 108 | + this.head++; |
| 109 | + this.length++; |
| 110 | + } |
| 111 | + |
| 112 | + private index(value: number) { |
| 113 | + if (!this.length) { |
| 114 | + return this._headConstraint(value); |
| 115 | + } |
| 116 | + |
| 117 | + const index = (value % this.length) + this.start; |
| 118 | + return this._headConstraint(index); |
46 | 119 | } |
47 | 120 | } |
0 commit comments