-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCircularBuffer.ts
201 lines (189 loc) · 4.92 KB
/
CircularBuffer.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
/**
* A circular buffer with constant time push and pop on both ends.
*
* @typeParam T The type of item contained inside.
*/
export class CircularBuffer<T> {
/**
* The index of the start of the buffer.
*/
private start: number;
/**
* The index of the end of the buffer.
*/
private end: number;
/**
* The data contained in the buffer.
*/
private data: T[];
/**
* The number of data elements in the buffer.
*/
private len: number;
/**
* Constructs a new `CircularBuffer` from a dataset.
*
* @param init The initial data.
*/
constructor(init: Iterable<T> = []) {
const data = [];
for (const elem of init) {
data.push(elem);
}
const totlen = data.length;
// initialize to a minimum length of 32 to get table doubling started faster
while (data.length < 32) {
data.push((null as unknown) as T);
}
this.data = data;
this.start = 0;
this.end = totlen % data.length;
this.len = totlen;
}
/**
* Returns the number of elements in the buffer.
*
* @returns The number of elements.
*/
size(): number {
return this.len;
}
/**
* Gets an element from the buffer. Errors on out of bound access.
*
* @param ind The index to get.
* @returns The element at the index.
*/
get(ind: number): T {
if (ind < 0 || ind >= this.size()) {
throw new RangeError("Index out of bounds.");
}
return this.data[(this.start + ind) % this.data.length];
}
/**
* Sets an element in the buffer. Errors on out of bounds access.
*
* @param ind The index to set.
* @param val The value to set to.
*/
set(ind: number, val: T) {
if (ind < 0 || ind >= this.size()) {
throw new RangeError("Index out of bounds.");
}
this.data[(this.start + ind) % this.data.length] = val;
}
/**
* Generates an iterator for the buffer.
*
* @returns An iterator.
*/
*[Symbol.iterator](): Generator<T> {
for (
let i = this.start;
i != this.end;
i = (i + 1) % this.data.length
) {
yield this.data[i];
}
}
/**
* Returns a shallow-copied array of the data.
*
* This is faster than collecting the iterator.
*
* @returns The array.
*/
toArray(): T[] {
if (this.start < this.end) {
return this.data.slice(this.start, this.end);
}
return [
...this.data.slice(this.start),
...this.data.slice(0, this.end),
];
}
/**
* Expands the buffer if needed.
*/
private possiblyExpand() {
if (this.size() >= this.data.length - 1) {
const newData = new Array(this.data.length * 2);
let i = 0;
for (const elem of this) {
newData[i] = elem;
i++;
}
this.start = 0;
this.end = i;
this.data = newData;
}
}
/**
* Shrinks the buffer if needed.
*/
private possiblyShrink() {
if (this.size() * 4 <= this.data.length) {
const newData = new Array(Math.floor(this.data.length / 2));
let i = 0;
for (const elem of this) {
newData[i] = elem;
i++;
}
this.start = 0;
this.end = i;
this.data = newData;
}
}
/**
* Pushes a value to the end of the buffer.
*
* @param val The value to push.
*/
pushEnd(val: T) {
this.possiblyExpand();
this.data[this.end] = val;
this.end = (this.end + 1) % this.data.length;
this.len++;
}
/**
* Pushes a value to the start of the buffer.
*
* @param val The value to push.
*/
pushStart(val: T) {
this.possiblyExpand();
this.start = (this.start - 1 + this.data.length) % this.data.length;
this.data[this.start] = val;
this.len++;
}
/**
* Pops a value from the start of the buffer.
*
* @returns The popped value.
*/
popEnd(): T {
if (this.size() == 0) {
throw new RangeError("Index out of bounds.");
}
this.end = (this.end - 1 + this.data.length) % this.data.length;
const ret = this.data[this.end];
this.possiblyShrink();
this.len--;
return ret;
}
/**
* Pops a value from the end of the buffer.
*
* @returns The popped value.
*/
popStart(): T {
if (this.size() == 0) {
throw new RangeError("Index out of bounds.");
}
const ret = this.data[this.start];
this.start = (this.start + 1) % this.data.length;
this.possiblyShrink();
this.len--;
return ret;
}
}