|
| 1 | +/** |
| 2 | + * Simplified abstraction adapted from denque (https://github.com/invertase/denque/tree/539105bb57854e997dd469221cdc52a0ad80e0a2) |
| 3 | + * License: Apache-2.0 (https://github.com/invertase/denque/blob/master/LICENSE) |
| 4 | + */ |
| 5 | + |
| 6 | +'use strict'; |
| 7 | + |
| 8 | +const MIN_SHRINK_TAIL = 10000; |
| 9 | + |
| 10 | +class RingQueue { |
| 11 | + constructor() { |
| 12 | + this._list = new Array(4); |
| 13 | + this._mask = 3; |
| 14 | + this._head = 0; |
| 15 | + this._tail = 0; |
| 16 | + } |
| 17 | + |
| 18 | + get length() { |
| 19 | + return (this._tail - this._head) & this._mask; |
| 20 | + } |
| 21 | + |
| 22 | + size() { |
| 23 | + return this.length; |
| 24 | + } |
| 25 | + |
| 26 | + isEmpty() { |
| 27 | + return this._head === this._tail; |
| 28 | + } |
| 29 | + |
| 30 | + push(item) { |
| 31 | + // biome-ignore lint/correctness/noUndeclaredVariables: arguments distinguishes push() from push(undefined) |
| 32 | + if (arguments.length === 0) { |
| 33 | + return this.length; |
| 34 | + } |
| 35 | + |
| 36 | + this._list[this._tail] = item; |
| 37 | + this._tail = (this._tail + 1) & this._mask; |
| 38 | + |
| 39 | + if (this._tail === this._head) { |
| 40 | + this._grow(); |
| 41 | + } |
| 42 | + |
| 43 | + return this.length; |
| 44 | + } |
| 45 | + |
| 46 | + unshift(item) { |
| 47 | + // biome-ignore lint/correctness/noUndeclaredVariables: arguments distinguishes unshift() from unshift(undefined) |
| 48 | + if (arguments.length === 0) { |
| 49 | + return this.length; |
| 50 | + } |
| 51 | + |
| 52 | + this._head = (this._head - 1) & this._mask; |
| 53 | + this._list[this._head] = item; |
| 54 | + |
| 55 | + if (this._tail === this._head) { |
| 56 | + this._grow(); |
| 57 | + } |
| 58 | + |
| 59 | + return this.length; |
| 60 | + } |
| 61 | + |
| 62 | + shift() { |
| 63 | + const head = this._head; |
| 64 | + |
| 65 | + if (head === this._tail) { |
| 66 | + return undefined; |
| 67 | + } |
| 68 | + |
| 69 | + const item = this._list[head]; |
| 70 | + this._list[head] = undefined; |
| 71 | + this._head = (head + 1) & this._mask; |
| 72 | + |
| 73 | + if ( |
| 74 | + head < 2 && |
| 75 | + this._tail > MIN_SHRINK_TAIL && |
| 76 | + this._tail <= this._list.length >>> 2 |
| 77 | + ) { |
| 78 | + this._shrink(); |
| 79 | + } |
| 80 | + |
| 81 | + return item; |
| 82 | + } |
| 83 | + |
| 84 | + pop() { |
| 85 | + const tail = this._tail; |
| 86 | + |
| 87 | + if (tail === this._head) { |
| 88 | + return undefined; |
| 89 | + } |
| 90 | + |
| 91 | + const capacity = this._list.length; |
| 92 | + this._tail = (tail - 1) & this._mask; |
| 93 | + |
| 94 | + const item = this._list[this._tail]; |
| 95 | + this._list[this._tail] = undefined; |
| 96 | + |
| 97 | + if (this._head < 2 && tail > MIN_SHRINK_TAIL && tail <= capacity >>> 2) { |
| 98 | + this._shrink(); |
| 99 | + } |
| 100 | + |
| 101 | + return item; |
| 102 | + } |
| 103 | + |
| 104 | + peekAt(index) { |
| 105 | + if (index !== (index | 0)) { |
| 106 | + return undefined; |
| 107 | + } |
| 108 | + |
| 109 | + if (index >= 0) { |
| 110 | + if (index >= this.length) { |
| 111 | + return undefined; |
| 112 | + } |
| 113 | + |
| 114 | + return this._list[(this._head + index) & this._mask]; |
| 115 | + } |
| 116 | + |
| 117 | + const size = this.length; |
| 118 | + |
| 119 | + if (index < -size) { |
| 120 | + return undefined; |
| 121 | + } |
| 122 | + |
| 123 | + return this._list[(this._head + index + size) & this._mask]; |
| 124 | + } |
| 125 | + |
| 126 | + get(index) { |
| 127 | + return this.peekAt(index); |
| 128 | + } |
| 129 | + |
| 130 | + peek() { |
| 131 | + if (this._head === this._tail) { |
| 132 | + return undefined; |
| 133 | + } |
| 134 | + |
| 135 | + return this._list[this._head]; |
| 136 | + } |
| 137 | + |
| 138 | + peekFront() { |
| 139 | + return this.peek(); |
| 140 | + } |
| 141 | + |
| 142 | + peekBack() { |
| 143 | + return this.peekAt(-1); |
| 144 | + } |
| 145 | + |
| 146 | + removeOne(index) { |
| 147 | + if (index !== (index | 0)) { |
| 148 | + return undefined; |
| 149 | + } |
| 150 | + |
| 151 | + const size = this.length; |
| 152 | + |
| 153 | + if (index >= size || index < -size) { |
| 154 | + return undefined; |
| 155 | + } |
| 156 | + |
| 157 | + if (index < 0) { |
| 158 | + index += size; |
| 159 | + } |
| 160 | + |
| 161 | + const mask = this._mask; |
| 162 | + let slot = (this._head + index) & mask; |
| 163 | + const item = this._list[slot]; |
| 164 | + const isCloserToHead = index < size / 2; |
| 165 | + |
| 166 | + if (isCloserToHead) { |
| 167 | + for (let moves = index; moves > 0; moves--) { |
| 168 | + const previous = (slot - 1) & mask; |
| 169 | + this._list[slot] = this._list[previous]; |
| 170 | + slot = previous; |
| 171 | + } |
| 172 | + |
| 173 | + this._list[slot] = undefined; |
| 174 | + this._head = (this._head + 1) & mask; |
| 175 | + } else { |
| 176 | + for (let moves = size - 1 - index; moves > 0; moves--) { |
| 177 | + const next = (slot + 1) & mask; |
| 178 | + this._list[slot] = this._list[next]; |
| 179 | + slot = next; |
| 180 | + } |
| 181 | + |
| 182 | + this._list[slot] = undefined; |
| 183 | + this._tail = (this._tail - 1) & mask; |
| 184 | + } |
| 185 | + |
| 186 | + return item; |
| 187 | + } |
| 188 | + |
| 189 | + remove(index, count) { |
| 190 | + if (index !== (index | 0)) { |
| 191 | + return undefined; |
| 192 | + } |
| 193 | + |
| 194 | + if (this._head === this._tail) { |
| 195 | + return undefined; |
| 196 | + } |
| 197 | + |
| 198 | + const size = this.length; |
| 199 | + |
| 200 | + if (index >= size || index < -size || count < 1) { |
| 201 | + return undefined; |
| 202 | + } |
| 203 | + |
| 204 | + if (index < 0) { |
| 205 | + index += size; |
| 206 | + } |
| 207 | + |
| 208 | + if (count === 1 || !count) { |
| 209 | + return [this.removeOne(index)]; |
| 210 | + } |
| 211 | + |
| 212 | + if (count !== (count | 0)) { |
| 213 | + return undefined; |
| 214 | + } |
| 215 | + |
| 216 | + if (index + count > size) { |
| 217 | + count = size - index; |
| 218 | + } |
| 219 | + |
| 220 | + const items = this.toArray(); |
| 221 | + const removed = items.splice(index, count); |
| 222 | + this._rebuild(items); |
| 223 | + |
| 224 | + return removed; |
| 225 | + } |
| 226 | + |
| 227 | + splice(index, count, ...newItems) { |
| 228 | + if (index !== (index | 0)) { |
| 229 | + return undefined; |
| 230 | + } |
| 231 | + |
| 232 | + const size = this.length; |
| 233 | + |
| 234 | + if (index < 0) { |
| 235 | + index += size; |
| 236 | + } |
| 237 | + |
| 238 | + if (index > size) { |
| 239 | + return undefined; |
| 240 | + } |
| 241 | + |
| 242 | + if (newItems.length === 0) { |
| 243 | + return this.remove(index, count); |
| 244 | + } |
| 245 | + |
| 246 | + if (index < 0) { |
| 247 | + return undefined; |
| 248 | + } |
| 249 | + |
| 250 | + const removalCount = count === undefined ? 1 : count; |
| 251 | + |
| 252 | + if (removalCount !== (removalCount | 0) || removalCount < 0) { |
| 253 | + return undefined; |
| 254 | + } |
| 255 | + |
| 256 | + const items = this.toArray(); |
| 257 | + let removed; |
| 258 | + |
| 259 | + if (removalCount === 0) { |
| 260 | + removed = []; |
| 261 | + items.splice(index, 0, ...newItems); |
| 262 | + } else if (index >= size) { |
| 263 | + removed = undefined; |
| 264 | + items.splice(index, 0, ...newItems); |
| 265 | + } else { |
| 266 | + removed = items.splice(index, removalCount, ...newItems); |
| 267 | + } |
| 268 | + |
| 269 | + this._rebuild(items); |
| 270 | + |
| 271 | + return removed; |
| 272 | + } |
| 273 | + |
| 274 | + clear() { |
| 275 | + this._list = new Array(this._list.length); |
| 276 | + this._head = 0; |
| 277 | + this._tail = 0; |
| 278 | + } |
| 279 | + |
| 280 | + toArray() { |
| 281 | + const head = this._head; |
| 282 | + const tail = this._tail; |
| 283 | + |
| 284 | + if (head <= tail) { |
| 285 | + return this._list.slice(head, tail); |
| 286 | + } |
| 287 | + |
| 288 | + const capacity = this._list.length; |
| 289 | + const items = new Array(this.length); |
| 290 | + let count = 0; |
| 291 | + |
| 292 | + for (let slot = head; slot < capacity; slot++) { |
| 293 | + items[count++] = this._list[slot]; |
| 294 | + } |
| 295 | + |
| 296 | + for (let slot = 0; slot < tail; slot++) { |
| 297 | + items[count++] = this._list[slot]; |
| 298 | + } |
| 299 | + |
| 300 | + return items; |
| 301 | + } |
| 302 | + |
| 303 | + _grow() { |
| 304 | + const list = this._list; |
| 305 | + const capacity = list.length; |
| 306 | + |
| 307 | + if (this._head === 0) { |
| 308 | + this._tail = capacity; |
| 309 | + list.length = capacity << 1; |
| 310 | + } else { |
| 311 | + const grown = new Array(capacity << 1); |
| 312 | + let count = 0; |
| 313 | + |
| 314 | + for (let slot = this._head; slot < capacity; slot++) { |
| 315 | + grown[count++] = list[slot]; |
| 316 | + } |
| 317 | + |
| 318 | + for (let slot = 0; slot < this._tail; slot++) { |
| 319 | + grown[count++] = list[slot]; |
| 320 | + } |
| 321 | + |
| 322 | + this._list = grown; |
| 323 | + this._head = 0; |
| 324 | + this._tail = capacity; |
| 325 | + } |
| 326 | + |
| 327 | + this._mask = (this._mask << 1) | 1; |
| 328 | + } |
| 329 | + |
| 330 | + _shrink() { |
| 331 | + this._list.length >>>= 1; |
| 332 | + this._mask >>>= 1; |
| 333 | + } |
| 334 | + |
| 335 | + _rebuild(items) { |
| 336 | + let capacity = this._list.length; |
| 337 | + |
| 338 | + while (items.length >= capacity) { |
| 339 | + capacity <<= 1; |
| 340 | + } |
| 341 | + |
| 342 | + this._list = new Array(capacity); |
| 343 | + this._mask = capacity - 1; |
| 344 | + this._head = 0; |
| 345 | + this._tail = items.length; |
| 346 | + |
| 347 | + for (let i = 0; i < items.length; i++) { |
| 348 | + this._list[i] = items[i]; |
| 349 | + } |
| 350 | + } |
| 351 | +} |
| 352 | + |
| 353 | +module.exports = RingQueue; |
0 commit comments