atomic-fns 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/.esdoc.json +2 -2
  2. package/README.md +48 -13
  3. package/core/collections/BTree.d.ts +229 -0
  4. package/core/collections/BTree.js +1165 -0
  5. package/core/collections/BTree.ts +1340 -0
  6. package/core/collections/Heap.d.ts +69 -0
  7. package/core/collections/Heap.js +181 -0
  8. package/core/collections/Heap.ts +189 -0
  9. package/core/collections/LRUCache.d.ts +131 -0
  10. package/core/collections/LRUCache.js +388 -0
  11. package/core/collections/LRUCache.ts +465 -0
  12. package/core/collections/SortedMap.d.ts +92 -0
  13. package/core/collections/SortedMap.js +130 -0
  14. package/core/collections/SortedMap.ts +146 -0
  15. package/core/collections/SortedSet.d.ts +63 -0
  16. package/core/collections/SortedSet.js +93 -0
  17. package/core/collections/SortedSet.ts +104 -0
  18. package/core/collections/SortedTree.d.ts +103 -0
  19. package/core/collections/SortedTree.js +141 -0
  20. package/core/collections/SortedTree.ts +160 -0
  21. package/core/collections/SplayTree.d.ts +105 -0
  22. package/core/collections/SplayTree.js +338 -0
  23. package/core/collections/SplayTree.ts +354 -0
  24. package/core/collections/abc.d.ts +152 -0
  25. package/core/collections/abc.js +39 -0
  26. package/core/collections/abc.ts +176 -0
  27. package/core/collections/deque.d.ts +110 -0
  28. package/core/collections/deque.js +285 -0
  29. package/core/collections/deque.ts +329 -0
  30. package/core/collections/frozenset.d.ts +19 -0
  31. package/core/collections/frozenset.js +33 -0
  32. package/core/collections/frozenset.ts +37 -0
  33. package/{src → core}/collections/index.d.ts +12 -161
  34. package/{src → core}/collections/index.js +13 -164
  35. package/{src → core}/collections/index.ts +13 -224
  36. package/core/collections/test.html +10 -0
  37. package/core/collections/typedArrays.d.ts +31 -0
  38. package/core/collections/typedArrays.js +143 -0
  39. package/core/collections/typedArrays.ts +163 -0
  40. package/{src → core}/datetime/index.d.ts +0 -0
  41. package/{src → core}/datetime/index.js +0 -0
  42. package/{src → core}/datetime/index.ts +0 -0
  43. package/core/datetime/test.html +0 -0
  44. package/{src → core}/functools/index.d.ts +2 -2
  45. package/{src → core}/functools/index.js +2 -2
  46. package/{src → core}/functools/index.ts +2 -2
  47. package/{src → core}/globals/index.d.ts +2 -7
  48. package/{src → core}/globals/index.js +5 -2
  49. package/{src → core}/globals/index.ts +6 -9
  50. package/{src → core}/index.d.ts +0 -0
  51. package/{src → core}/index.js +0 -0
  52. package/{src → core}/index.ts +0 -0
  53. package/{src → core}/itertools/index.d.ts +9 -7
  54. package/{src → core}/itertools/index.js +25 -9
  55. package/{src → core}/itertools/index.ts +39 -16
  56. package/{src → core}/math/index.d.ts +0 -0
  57. package/{src → core}/math/index.js +0 -0
  58. package/{src → core}/math/index.ts +0 -0
  59. package/core/operators/index.d.ts +148 -0
  60. package/core/operators/index.js +271 -0
  61. package/core/operators/index.ts +282 -0
  62. package/{src → core}/random/index.d.ts +0 -0
  63. package/{src → core}/random/index.js +0 -0
  64. package/{src → core}/random/index.ts +0 -0
  65. package/package.json +7 -4
  66. package/tests/BTree.spec.ts +143 -0
  67. package/tests/Cache.spec.ts +161 -0
  68. package/tests/Heap.spec.ts +66 -0
  69. package/tests/SortedMap.spec.ts +144 -0
  70. package/tests/SortedSet.spec.ts +84 -0
  71. package/tests/SortedTree.spec.ts +164 -0
  72. package/tests/SplayTree.spec.ts +121 -0
  73. package/tests/collections.spec.ts +1 -1
  74. package/tests/deque.spec.ts +107 -0
  75. package/tests/globals.spec.ts +2 -2
  76. package/tests/itertools.spec.ts +2 -2
  77. package/tests/math.spec.ts +1 -1
  78. package/tests/operators.spec.ts +5 -5
  79. package/tests/random.spec.ts +10 -10
  80. package/tsconfig.json +10 -10
  81. package/tsconfig.node.json +2 -2
  82. package/tsconfig.xo.json +2 -2
  83. package/src/operators/index.d.ts +0 -31
  84. package/src/operators/index.js +0 -171
  85. package/src/operators/index.ts +0 -175
@@ -0,0 +1,1340 @@
1
+ import { Iteratee, KeyError } from '../globals/index.js'
2
+ import { compare, Comparer } from '../operators/index.js'
3
+ import { Mapping } from './abc.js'
4
+
5
+ const Delete = { delete: true }
6
+ const DeleteRange = () => Delete
7
+ const Break = { break: true }
8
+ const EmptyArray: any[] = []
9
+ const ReusedArray: any[] = [] // assumed thread-local
10
+
11
+ // Optimization: this array of `undefined`s is used instead of a normal
12
+ // array of values in nodes where `undefined` is the only value.
13
+ // Its length is extended to max node size on first use; since it can
14
+ // be shared between trees with different maximums, its length can only
15
+ // increase, never decrease. Its type should be undefined[] but strangely
16
+ // TypeScript won't allow the comparison V[] === undefined[]. To prevent
17
+ // users from making this array too large, BTree has a maximum node size.
18
+ //
19
+ // FAQ: undefVals[i] is already undefined, so why increase the array size?
20
+ // Reading outside the bounds of an array is relatively slow because it
21
+ // has the side effect of scanning the prototype chain.
22
+ const undefVals: any[] = []
23
+
24
+ /**
25
+ * Leaf nodes and base class for internals.
26
+ * Based on the `sorted-btree` implementation by David Piepgrass (MIT License).
27
+ * @private
28
+ */
29
+ class BNode<K, V = any> {
30
+ // If this is an internal node, _keys[i] is the highest key in children[i].
31
+ keys: K[]
32
+ values: V[]
33
+ // True if this node might be within multiple `BTree`s (or have multiple parents).
34
+ // If so, it must be cloned before being mutated to avoid changing an unrelated tree.
35
+ // This is transitive: if it's true, children are also shared even if `isShared!=true`
36
+ // in those children. (Certain operations will propagate isShared=true to children.)
37
+ isShared?: boolean
38
+ get isLeaf() {
39
+ return (this as any).children === undefined
40
+ }
41
+
42
+ constructor(keys: K[] = [], values?: V[]) {
43
+ this.keys = keys
44
+ this.values = values || undefVals
45
+ this.isShared = undefined
46
+ }
47
+
48
+ /// ////////////////////////////////////////////////////////////////////////
49
+ // Shared methods /////////////////////////////////////////////////////////
50
+
51
+ maxKey() {
52
+ return this.keys[this.keys.length - 1]
53
+ }
54
+
55
+ // If key not found, returns i^failXor where i is the insertion index.
56
+ // Callers that don't care whether there was a match will set failXor=0.
57
+ indexOf(key: K, failXor: number, cmp: (a: K, b: K) => number) {
58
+ const keys = this.keys
59
+ let lo = 0
60
+ let hi = keys.length
61
+ let mid = hi >> 1
62
+ while (lo < hi) {
63
+ const c = cmp(keys[mid], key)
64
+ if (c < 0) lo = mid + 1
65
+ else if (c > 0) hi = mid // key < keys[mid]
66
+ else if (c === 0) return mid
67
+ else {
68
+ // c is NaN or otherwise invalid
69
+ // eslint-disable-next-line
70
+ if (key === key)
71
+ // at least the search key is not NaN
72
+ return keys.length
73
+ // eslint-disable-next-line
74
+ else throw new KeyError(`Btree found an invalid key: ${key}`)
75
+ }
76
+ mid = (lo + hi) >> 1
77
+ }
78
+ return mid ^ failXor
79
+ }
80
+
81
+ /// //////////////////////////////////////////////////////////////////////////
82
+ // Leaf Node: misc //////////////////////////////////////////////////////////
83
+
84
+ minKey(): K | undefined {
85
+ return this.keys[0]
86
+ }
87
+
88
+ minPair(reusedArray: [K, V]): [K, V] | undefined {
89
+ if (this.keys.length === 0) return undefined
90
+ reusedArray[0] = this.keys[0]
91
+ reusedArray[1] = this.values[0]
92
+ return reusedArray
93
+ }
94
+
95
+ maxPair(reusedArray: [K, V]): [K, V] | undefined {
96
+ if (this.keys.length === 0) return undefined
97
+ const lastIndex = this.keys.length - 1
98
+ reusedArray[0] = this.keys[lastIndex]
99
+ reusedArray[1] = this.values[lastIndex]
100
+ return reusedArray
101
+ }
102
+
103
+ clone(): BNode<K, V> {
104
+ const v = this.values
105
+ return new BNode<K, V>(this.keys.slice(0), v === undefVals ? v : v.slice(0))
106
+ }
107
+
108
+ greedyClone(force?: boolean): BNode<K, V> {
109
+ return this.isShared && !force ? this : this.clone()
110
+ }
111
+
112
+ get(key: K, defaultValue: V | undefined, tree: BTree<K, V>): V | undefined {
113
+ const i = this.indexOf(key, -1, tree._compare)
114
+ return i < 0 ? defaultValue : this.values[i]
115
+ }
116
+
117
+ getPairOrNextLower(
118
+ key: K,
119
+ compare: (a: K, b: K) => number,
120
+ inclusive: boolean,
121
+ reusedArray: [K, V]
122
+ ): [K, V] | undefined {
123
+ const i = this.indexOf(key, -1, compare)
124
+ const indexOrLower = i < 0 ? ~i - 1 : inclusive ? i : i - 1
125
+ if (indexOrLower >= 0) {
126
+ reusedArray[0] = this.keys[indexOrLower]
127
+ reusedArray[1] = this.values[indexOrLower]
128
+ return reusedArray
129
+ }
130
+ return undefined
131
+ }
132
+
133
+ getPairOrNextHigher(
134
+ key: K,
135
+ compare: (a: K, b: K) => number,
136
+ inclusive: boolean,
137
+ reusedArray: [K, V]
138
+ ): [K, V] | undefined {
139
+ const i = this.indexOf(key, -1, compare)
140
+ const indexOrLower = i < 0 ? ~i : inclusive ? i : i + 1
141
+ const keys = this.keys
142
+ if (indexOrLower < keys.length) {
143
+ reusedArray[0] = keys[indexOrLower]
144
+ reusedArray[1] = this.values[indexOrLower]
145
+ return reusedArray
146
+ }
147
+ return undefined
148
+ }
149
+
150
+ checkValid(depth: number, tree: BTree<K, V>, baseIndex: number): number {
151
+ const kL = this.keys.length
152
+ const vL = this.values.length
153
+ assert(
154
+ this.values === undefVals ? kL <= vL : kL === vL,
155
+ 'keys/values length mismatch: depth',
156
+ depth,
157
+ 'with lengths',
158
+ kL,
159
+ vL,
160
+ 'and baseIndex',
161
+ baseIndex
162
+ )
163
+ // Note: we don't check for "node too small" because sometimes a node
164
+ // can legitimately have size 1. This occurs if there is a batch
165
+ // deletion, leaving a node of size 1, and the siblings are full so
166
+ // it can't be merged with adjacent nodes. However, the parent will
167
+ // verify that the average node size is at least half of the maximum.
168
+ assert(depth === 0 || kL > 0, 'empty leaf at depth', depth, 'and baseIndex', baseIndex)
169
+ return kL
170
+ }
171
+
172
+ /// //////////////////////////////////////////////////////////////////////////
173
+ // Leaf Node: set & node splitting //////////////////////////////////////////
174
+
175
+ set(key: K, value: V, overwrite: boolean | undefined, tree: BTree<K, V>): boolean | BNode<K, V> {
176
+ let i = this.indexOf(key, -1, tree._compare)
177
+ if (i < 0) {
178
+ // key does not exist yet
179
+ i = ~i
180
+ tree.count++
181
+
182
+ if (this.keys.length < tree._maxNodeSize) {
183
+ return this.insertInLeaf(i, key, value, tree)
184
+ } else {
185
+ // This leaf node is full and must split
186
+ const newRightSibling = this.splitOffRightSide()
187
+
188
+ // eslint-disable-next-line
189
+ let target: BNode<K, V> = this
190
+ if (i > this.keys.length) {
191
+ i -= this.keys.length
192
+ target = newRightSibling
193
+ }
194
+ target.insertInLeaf(i, key, value, tree)
195
+ return newRightSibling
196
+ }
197
+ } else {
198
+ // Key already exists
199
+ if (overwrite !== false) {
200
+ if (value !== undefined) this.reifyValues()
201
+ // usually this is a no-op, but some users may wish to edit the key
202
+ this.keys[i] = key
203
+ this.values[i] = value
204
+ }
205
+ return false
206
+ }
207
+ }
208
+
209
+ reifyValues() {
210
+ if (this.values === undefVals) return (this.values = this.values.slice(0, this.keys.length))
211
+ return this.values
212
+ }
213
+
214
+ insertInLeaf(i, key: K, value: V, tree: BTree<K, V>) {
215
+ this.keys.splice(i, 0, key)
216
+ if (this.values === undefVals) {
217
+ while (undefVals.length < tree._maxNodeSize) undefVals.push(undefined)
218
+ if (value === undefined) {
219
+ return true
220
+ } else {
221
+ this.values = undefVals.slice(0, this.keys.length - 1)
222
+ }
223
+ }
224
+ this.values.splice(i, 0, value)
225
+ return true
226
+ }
227
+
228
+ takeFromRight(rhs: BNode<K, V>) {
229
+ // Reminder: parent node must update its copy of key for this node
230
+ // assert: neither node is shared
231
+ // assert rhs.keys.length > (maxNodeSize/2 && this.keys.length<maxNodeSize)
232
+ let v = this.values
233
+ if (rhs.values === undefVals) {
234
+ if (v !== undefVals) v.push(undefined as any)
235
+ } else {
236
+ v = this.reifyValues()
237
+ // eslint-disable-next-line
238
+ v.push(rhs.values.shift()!)
239
+ }
240
+ // eslint-disable-next-line
241
+ this.keys.push(rhs.keys.shift()!)
242
+ }
243
+
244
+ takeFromLeft(lhs: BNode<K, V>) {
245
+ // Reminder: parent node must update its copy of key for this node
246
+ // assert: neither node is shared
247
+ // assert rhs.keys.length > (maxNodeSize/2 && this.keys.length<maxNodeSize)
248
+ let v = this.values
249
+ if (lhs.values === undefVals) {
250
+ if (v !== undefVals) v.unshift()
251
+ } else {
252
+ v = this.reifyValues()
253
+ // eslint-disable-next-line
254
+ v.unshift(lhs.values.pop()!)
255
+ }
256
+ // eslint-disable-next-line
257
+ this.keys.unshift(lhs.keys.pop()!)
258
+ }
259
+
260
+ splitOffRightSide(): BNode<K, V> {
261
+ // Reminder: parent node must update its copy of key for this node
262
+ const half = this.keys.length >> 1
263
+ const keys = this.keys.splice(half)
264
+ const values = this.values === undefVals ? undefVals : this.values.splice(half)
265
+ return new BNode<K, V>(keys, values)
266
+ }
267
+
268
+ /// //////////////////////////////////////////////////////////////////////////
269
+ // Leaf Node: scanning & deletions //////////////////////////////////////////
270
+
271
+ forRange<R>(
272
+ low: K,
273
+ high: K,
274
+ includeHigh: boolean | undefined,
275
+ editMode: boolean,
276
+ tree: BTree<K, V>,
277
+ count: number,
278
+ onFound?: Iteratee<K, V>
279
+ ): any | number {
280
+ const cmp = tree._compare
281
+ let iLow, iHigh
282
+ if (high === low) {
283
+ if (!includeHigh) return count
284
+ iHigh = (iLow = this.indexOf(low, -1, cmp)) + 1
285
+ if (iLow < 0) return count
286
+ } else {
287
+ iLow = this.indexOf(low, 0, cmp)
288
+ iHigh = this.indexOf(high, -1, cmp)
289
+ if (iHigh < 0) iHigh = ~iHigh
290
+ else if (includeHigh === true) iHigh++
291
+ }
292
+ const keys = this.keys
293
+ const values = this.values
294
+ if (onFound !== undefined) {
295
+ for (let i = iLow; i < iHigh; i++) {
296
+ const key = keys[i]
297
+ const result = onFound(key, values[i], count++)
298
+ if (result !== undefined) {
299
+ if (editMode) {
300
+ if (key !== keys[i] || this.isShared === true)
301
+ throw new Error('BTree illegally changed or cloned in editRange')
302
+ if (result.delete) {
303
+ this.keys.splice(i, 1)
304
+ if (this.values !== undefVals) this.values.splice(i, 1)
305
+ tree.count--
306
+ i--
307
+ iHigh--
308
+ } else if (result.value) {
309
+ values[i] = result.value
310
+ }
311
+ }
312
+ if (result.break !== undefined) return result
313
+ }
314
+ }
315
+ } else count += iHigh - iLow
316
+ return count
317
+ }
318
+
319
+ /** Adds entire contents of right-hand sibling (rhs is left unchanged) */
320
+ mergeSibling(rhs: BNode<K, V>, _: number) {
321
+ this.keys.push.apply(this.keys, rhs.keys)
322
+ if (this.values === undefVals) {
323
+ if (rhs.values === undefVals) return
324
+ this.values = this.values.slice(0, this.keys.length)
325
+ }
326
+ this.values.push.apply(this.values, rhs.reifyValues())
327
+ }
328
+ }
329
+
330
+ const EmptyLeaf = new BNode<any, any>()
331
+ EmptyLeaf.isShared = true
332
+
333
+ function assert(exp: boolean, ...args: any[]) {
334
+ if (!exp) {
335
+ args.unshift('B+ tree') // at beginning of message
336
+ throw new Error(args.join(' '))
337
+ }
338
+ }
339
+
340
+ export class BTree<K = any, V = any> extends Mapping<K, V> {
341
+ private root: BNode<K, V> = EmptyLeaf as BNode<K, V>
342
+ count: number = 0
343
+ _maxNodeSize: number
344
+
345
+ /**
346
+ * provides a total order over keys (and a strict partial order over the type K)
347
+ * @returns a negative value if a < b, 0 if a === b and a positive value if a > b
348
+ */
349
+ _compare: Comparer
350
+
351
+ /**
352
+ * Initializes an empty B+ tree.
353
+ * @param {?Iterable<[K, V]>} [entries] The initial key value pairs.
354
+ * @param compareFn Custom function to compare pairs of elements in the tree.
355
+ * If not specified, defaultComparator will be used which is valid as long as K extends DefaultComparable.
356
+ * @param entries A set of key-value pairs to initialize the tree
357
+ * @param {number} [maxNodeSize=64] Branching factor (maximum items or children per node)
358
+ * Must be in range 4..256. If undefined or <4 then default is used; if >256 then 256.
359
+ */
360
+ public constructor(entries?: Iterable<[K, V]>, compareFn?: Comparer, maxNodeSize: number = 64) {
361
+ super()
362
+ this._maxNodeSize = maxNodeSize >= 4 ? Math.min(maxNodeSize, 256) : 64
363
+ this._compare = compareFn || compare
364
+ if (entries) this.extend(entries)
365
+ }
366
+
367
+ /** Gets the number of key-value pairs in the tree. */
368
+ get size() {
369
+ return this.count
370
+ }
371
+
372
+ /** Releases the tree so that its size is 0. */
373
+ clear() {
374
+ this.root = EmptyLeaf as BNode<K, V>
375
+ this.count = 0
376
+ }
377
+
378
+ // /** Runs a function for each key-value pair, in order from smallest to
379
+ // * largest key. For compatibility with ES6 Map, the argument order to
380
+ // * the callback is backwards: value first, then key. Call forEachPair
381
+ // * instead to receive the key as the first argument.
382
+ // * @returns the number of values that were sent to the callback,
383
+ // * or the R value if the callback returned {break:R}. */
384
+ // forEach<R = number>(iteratee: Iteratee<V, K>): R | number {
385
+ // return this.forEachPair((k, v) => iteratee(v as V, k, this))
386
+ // }
387
+
388
+ // /** Runs a function for each key-value pair, in order from smallest to
389
+ // * largest key. The callback can return {break:R} (where R is any value
390
+ // * except undefined) to stop immediately and return R from forEachPair.
391
+ // * @param onFound A function that is called for each key-value pair. This
392
+ // * function can return {break:R} to stop early with result R.
393
+ // * The reason that you must return {break:R} instead of simply R
394
+ // * itself is for consistency with editRange(), which allows
395
+ // * multiple actions, not just breaking.
396
+ // * @param initialCounter This is the value of the third argument of
397
+ // * `onFound` the first time it is called. The counter increases
398
+ // * by one each time `onFound` is called. Default value: 0
399
+ // * @returns the number of pairs sent to the callback (plus initialCounter,
400
+ // * if you provided one). If the callback returned {break:R} then
401
+ // * the R value is returned instead. */
402
+ // private forEachPair<R = number>(callback: Iteratee<K, V>, initialCounter?: number): R | number {
403
+ // const low = this.minKey()
404
+ // const high = this.maxKey()
405
+ // // eslint-disable-next-line
406
+ // return this.rangeForEach(low!, high, true, callback, initialCounter)
407
+ // }
408
+
409
+ /**
410
+ * Finds a pair in the tree and returns the associated value.
411
+ * @param defaultValue a value to return if the key was not found.
412
+ * @returns the value, or defaultValue if the key was not found.
413
+ * @description Computational complexity: O(log size)
414
+ */
415
+ get(key: K, defaultValue?: V): V | undefined {
416
+ return this.root.get(key, defaultValue, this)
417
+ }
418
+
419
+ /**
420
+ * Adds or overwrites a key-value pair in the B+ tree.
421
+ * @param key the key is used to determine the sort order of
422
+ * data in the tree.
423
+ * @param value data to associate with the key (optional)
424
+ * @returns true if a new key-value pair was added.
425
+ * @description Computational complexity: O(log size)
426
+ * Note: when overwriting a previous entry, the key is updated
427
+ * as well as the value. This has no effect unless the new key
428
+ * has data that does not affect its sort order.
429
+ */
430
+ set(key: K, value: V): boolean {
431
+ if (this.root.isShared) this.root = this.root.clone()
432
+ const result = this.root.set(key, value, true, this)
433
+ if (result === true || result === false) return result
434
+ // Root node has split, so create a new root node.
435
+ this.root = new BNodeInternal<K, V>([this.root, result])
436
+ return true
437
+ }
438
+
439
+ add(key: K) {
440
+ return this.set(key, null as V)
441
+ }
442
+
443
+ /**
444
+ * Returns true if the key exists in the B+ tree, false if not.
445
+ * @param key Key to detect
446
+ * @description Computational complexity: O(log size)
447
+ */
448
+ contains(key: K): boolean {
449
+ return this.get(key) !== undefined
450
+ }
451
+
452
+ /**
453
+ * Removes a single key-value pair from the B+ tree.
454
+ * @param key Key to find
455
+ * @returns true if a pair was found and removed, false otherwise.
456
+ * @description Computational complexity: O(log size)
457
+ */
458
+ remove(key: K): boolean {
459
+ return this.rangeUpdate(key, key, true, DeleteRange) !== 0
460
+ }
461
+
462
+ /** Returns an iterator that provides items in order (ascending order if
463
+ * the collection's comparator uses ascending order, as is the default.)
464
+ * @param lowestKey First key to be iterated, or undefined to start at
465
+ * minKey(). If the specified key doesn't exist then iteration
466
+ * starts at the next higher key (according to the comparator).
467
+ * @param reusedArray Optional array used repeatedly to store key-value
468
+ * pairs, to avoid creating a new array on every iteration.
469
+ */
470
+ entries(lowestKey?: K, reusedArray?: Array<K | V>): IterableIterator<[K, V]> {
471
+ const info = this.findPath(lowestKey)
472
+ if (info === undefined) return iterator<[K, V]>()
473
+ let { nodequeue, nodeindex, leaf } = info
474
+ let state = reusedArray !== undefined ? 1 : 0
475
+ let i = lowestKey === undefined ? -1 : leaf.indexOf(lowestKey, 0, this._compare) - 1
476
+
477
+ return iterator<[K, V]>(() => {
478
+ // eslint-disable-next-line
479
+ jump: while (true) {
480
+ switch (state) {
481
+ case 0:
482
+ if (++i < leaf.keys.length)
483
+ return { done: false, value: [leaf.keys[i], leaf.values[i]] }
484
+ state = 2
485
+ continue
486
+ case 1:
487
+ if (++i < leaf.keys.length) {
488
+ // eslint-disable-next-line
489
+ reusedArray![0] = leaf.keys[i]!
490
+ // eslint-disable-next-line
491
+ reusedArray![1] = leaf.values[i]!
492
+ return { done: false, value: reusedArray as [K, V] }
493
+ }
494
+ state = 2
495
+ // eslint-disable-next-line
496
+ case 2:
497
+ // Advance to the next leaf node
498
+ // eslint-disable-next-line
499
+ let level = -1
500
+ while (true) {
501
+ if (++level >= nodequeue.length) {
502
+ state = 3
503
+ // eslint-disable-next-line
504
+ continue jump
505
+ }
506
+ if (++nodeindex[level] < nodequeue[level].length) break
507
+ }
508
+ for (; level > 0; level--) {
509
+ nodequeue[level - 1] = (
510
+ nodequeue[level][nodeindex[level]] as BNodeInternal<K, V>
511
+ ).children
512
+ nodeindex[level - 1] = 0
513
+ }
514
+ leaf = nodequeue[0][nodeindex[0]]
515
+ i = -1
516
+ state = reusedArray !== undefined ? 1 : 0
517
+ continue
518
+ case 3:
519
+ return { done: true, value: undefined }
520
+ }
521
+ }
522
+ })
523
+ }
524
+
525
+ /** Returns an iterator that provides items in reversed order.
526
+ * @param highestKey Key at which to start iterating, or undefined to
527
+ * start at maxKey(). If the specified key doesn't exist then iteration
528
+ * starts at the next lower key (according to the comparator).
529
+ * @param reusedArray Optional array used repeatedly to store key-value
530
+ * pairs, to avoid creating a new array on every iteration.
531
+ * @param skipHighest Iff this flag is true and the highestKey exists in the
532
+ * collection, the pair matching highestKey is skipped, not iterated.
533
+ */
534
+ reversed(
535
+ highestKey?: K,
536
+ reusedArray?: Array<K | V>,
537
+ skipHighest?: boolean
538
+ ): IterableIterator<[K, V]> {
539
+ if (highestKey === undefined) {
540
+ highestKey = this.maxKey()
541
+ skipHighest = undefined
542
+ if (highestKey === undefined) return iterator<[K, V]>() // collection is empty
543
+ }
544
+ // eslint-disable-next-line
545
+ let { nodequeue, nodeindex, leaf } = this.findPath(highestKey)! || this.findPath(this.maxKey())!
546
+ assert(!nodequeue[0] || leaf === nodequeue[0][nodeindex[0]], 'wat!')
547
+ let i = leaf.indexOf(highestKey, 0, this._compare)
548
+ if (!skipHighest && i < leaf.keys.length && this._compare(leaf.keys[i], highestKey) <= 0) i++
549
+ let state = reusedArray !== undefined ? 1 : 0
550
+
551
+ return iterator<[K, V]>(() => {
552
+ // eslint-disable-next-line
553
+ jump: while (true) {
554
+ switch (state) {
555
+ case 0:
556
+ if (--i >= 0) return { done: false, value: [leaf.keys[i], leaf.values[i]] }
557
+ state = 2
558
+ continue
559
+ case 1:
560
+ if (--i >= 0) {
561
+ // eslint-disable-next-line
562
+ reusedArray![0] = leaf.keys[i]!
563
+ // eslint-disable-next-line
564
+ reusedArray![1] = leaf.values[i]!
565
+ return { done: false, value: reusedArray as [K, V] }
566
+ }
567
+ state = 2
568
+ // eslint-disable-next-line
569
+ case 2:
570
+ // Advance to the next leaf node
571
+ // eslint-disable-next-line
572
+ let level = -1
573
+ while (true) {
574
+ if (++level >= nodequeue.length) {
575
+ state = 3
576
+ // eslint-disable-next-line
577
+ continue jump
578
+ }
579
+ if (--nodeindex[level] >= 0) break
580
+ }
581
+ for (; level > 0; level--) {
582
+ nodequeue[level - 1] = (
583
+ nodequeue[level][nodeindex[level]] as BNodeInternal<K, V>
584
+ ).children
585
+ nodeindex[level - 1] = nodequeue[level - 1].length - 1
586
+ }
587
+ leaf = nodequeue[0][nodeindex[0]]
588
+ i = leaf.keys.length
589
+ state = reusedArray !== undefined ? 1 : 0
590
+ continue
591
+ case 3:
592
+ return { done: true, value: undefined }
593
+ }
594
+ }
595
+ })
596
+ }
597
+
598
+ /* Used by entries() and entriesReversed() to prepare to start iterating.
599
+ * It develops a "node queue" for each non-leaf level of the tree.
600
+ * Levels are numbered "bottom-up" so that level 0 is a list of leaf
601
+ * nodes from a low-level non-leaf node. The queue at a given level L
602
+ * consists of nodequeue[L] which is the children of a BNodeInternal,
603
+ * and nodeindex[L], the current index within that child list, such
604
+ * such that nodequeue[L-1] === nodequeue[L][nodeindex[L]].children.
605
+ * (However inside this function the order is reversed.)
606
+ * @private
607
+ */
608
+ private findPath(
609
+ key?: K
610
+ ): { nodequeue: Array<Array<BNode<K, V>>>; nodeindex: number[]; leaf: BNode<K, V> } | undefined {
611
+ let nextnode = this.root
612
+ let nodequeue: Array<Array<BNode<K, V>>>, nodeindex: number[]
613
+
614
+ if (nextnode.isLeaf) {
615
+ // use preallocated empty
616
+ nodequeue = EmptyArray
617
+ nodeindex = EmptyArray
618
+ } else {
619
+ nodequeue = []
620
+ nodeindex = []
621
+ for (let d = 0; !nextnode.isLeaf; d++) {
622
+ nodequeue[d] = (nextnode as BNodeInternal<K, V>).children
623
+ nodeindex[d] = key === undefined ? 0 : nextnode.indexOf(key, 0, this._compare)
624
+ if (nodeindex[d] >= nodequeue[d].length) return // first key > maxKey()
625
+ nextnode = nodequeue[d][nodeindex[d]]
626
+ }
627
+ nodequeue.reverse()
628
+ nodeindex.reverse()
629
+ }
630
+ return { nodequeue, nodeindex, leaf: nextnode }
631
+ }
632
+
633
+ /** Returns a new iterator for iterating the keys of each pair in ascending order.
634
+ * @param firstKey: Minimum key to include in the output. */
635
+ keys(firstKey?: K): IterableIterator<K> {
636
+ const it = this.entries(firstKey, ReusedArray)
637
+ return iterator<K>(() => {
638
+ const n: IteratorResult<any> = it.next()
639
+ if (n.value) n.value = n.value[0]
640
+ return n
641
+ })
642
+ }
643
+
644
+ /** Gets the lowest key in the tree. Complexity: O(log size) */
645
+ minKey() {
646
+ return this.root.minKey()
647
+ }
648
+
649
+ /** Gets the highest key in the tree. Complexity: O(1) */
650
+ maxKey() {
651
+ return this.root.maxKey()
652
+ }
653
+
654
+ /** Returns a new iterator for iterating the values of each pair in order by key.
655
+ * @param firstKey: Minimum key whose associated value is included in the output. */
656
+ values(firstKey?: K): IterableIterator<V> {
657
+ const it = this.entries(firstKey, ReusedArray)
658
+ return iterator<V>(() => {
659
+ const n: IteratorResult<any> = it.next()
660
+ if (n.value) n.value = n.value[1]
661
+ return n
662
+ })
663
+ }
664
+
665
+ /// //////////////////////////////////////////////////////////////////////////
666
+ // Additional methods ///////////////////////////////////////////////////////
667
+
668
+ /** Returns the maximum number of children/values before nodes will split. */
669
+ get maxNodeSize() {
670
+ return this._maxNodeSize
671
+ }
672
+
673
+ // /** Quickly clones the tree by marking the root node as shared.
674
+ // * Both copies remain editable. When you modify either copy, any
675
+ // * nodes that are shared (or potentially shared) between the two
676
+ // * copies are cloned so that the changes do not affect other copies.
677
+ // * This is known as copy-on-write behavior, or "lazy copying". */
678
+ // clone(): BTree<K, V> {
679
+ // this.root.isShared = true
680
+ // let result = new BTree<K, V>(undefined, this._compare, this._maxNodeSize)
681
+ // result.root = this.root
682
+ // result.count = this.count
683
+ // return result
684
+ // }
685
+
686
+ /** Performs a deep cloning of the tree, immediately duplicating any nodes that are
687
+ * not currently marked as shared, in order to avoid marking any
688
+ * additional nodes as shared.
689
+ * @param force Clone all nodes, even shared ones.
690
+ */
691
+ clone(force = false): BTree<K, V> {
692
+ const result = new BTree<K, V>(undefined, this._compare, this._maxNodeSize)
693
+ result.root = this.root.greedyClone(force)
694
+ result.count = this.count
695
+ return result
696
+ }
697
+
698
+ /** Gets an array filled with the contents of the tree, sorted by key */
699
+ toArray(): Array<[K, V]> {
700
+ const min = this.minKey()
701
+ const max = this.maxKey()
702
+ const maxLength = 2147483647
703
+ if (min !== undefined) return this.keysRange(min, max, true, maxLength)
704
+ return []
705
+ }
706
+
707
+ /** Gets a string representing the tree's data based on toArray(). */
708
+ toString() {
709
+ return this.toArray().toString()
710
+ }
711
+
712
+ /** Returns the next pair whose key is larger than the specified key (or undefined if there is none).
713
+ * If key === undefined, this function returns the lowest pair.
714
+ * @param key The key to search for.
715
+ * @param reusedArray Optional array used repeatedly to store key-value pairs, to
716
+ * avoid creating a new array on every iteration.
717
+ */
718
+ lowerBound(key: K | undefined, reusedArray?: [K, V]): [K, V] | undefined {
719
+ reusedArray = reusedArray || ([] as unknown as [K, V])
720
+ if (key === undefined) {
721
+ return this.root.minPair(reusedArray)
722
+ }
723
+ return this.root.getPairOrNextHigher(key, this._compare, false, reusedArray)
724
+ }
725
+
726
+ /** Returns the next key larger than the specified key, or undefined if there is none.
727
+ * Also, nextHigherKey(undefined) returns the lowest key.
728
+ */
729
+ // nextHigherKey(key: K | undefined): K | undefined {
730
+ // let p = this.after(key, ReusedArray as [K, V])
731
+ // return p && p[0]
732
+ // }
733
+
734
+ /** Returns the next pair whose key is smaller than the specified key (or undefined if there is none).
735
+ * If key === undefined, this function returns the highest pair.
736
+ * @param key The key to search for.
737
+ * @param reusedArray Optional array used repeatedly to store key-value pairs, to
738
+ * avoid creating a new array each time you call this method.
739
+ */
740
+ upperBound(key: K | undefined, reusedArray?: [K, V]): [K, V] | undefined {
741
+ reusedArray = reusedArray || ([] as unknown as [K, V])
742
+ if (key === undefined) {
743
+ return this.root.maxPair(reusedArray)
744
+ }
745
+ return this.root.getPairOrNextLower(key, this._compare, false, reusedArray)
746
+ }
747
+
748
+ /** Returns the next key smaller than the specified key, or undefined if there is none.
749
+ * Also, nextLowerKey(undefined) returns the highest key.
750
+ */
751
+ // nextLowerKey(key: K | undefined): K | undefined {
752
+ // let p = this.before(key, ReusedArray as [K, V])
753
+ // return p && p[0]
754
+ // }
755
+
756
+ /** Returns the key-value pair associated with the supplied key if it exists
757
+ * or the pair associated with the next lower pair otherwise. If there is no
758
+ * next lower pair, undefined is returned.
759
+ * @param key The key to search for.
760
+ * @param reusedArray Optional array used repeatedly to store key-value pairs, to
761
+ * avoid creating a new array each time you call this method.
762
+ * */
763
+ floor(key: K, reusedArray?: [K, V]): [K, V] | undefined {
764
+ return this.root.getPairOrNextLower(
765
+ key,
766
+ this._compare,
767
+ true,
768
+ reusedArray || ([] as unknown as [K, V])
769
+ )
770
+ }
771
+
772
+ /** Returns the key-value pair associated with the supplied key if it exists
773
+ * or the pair associated with the next lower pair otherwise. If there is no
774
+ * next lower pair, undefined is returned.
775
+ * @param key The key to search for.
776
+ * @param reusedArray Optional array used repeatedly to store key-value pairs, to
777
+ * avoid creating a new array each time you call this method.
778
+ * */
779
+ ceiling(key: K, reusedArray?: [K, V]): [K, V] | undefined {
780
+ return this.root.getPairOrNextHigher(
781
+ key,
782
+ this._compare,
783
+ true,
784
+ reusedArray || ([] as unknown as [K, V])
785
+ )
786
+ }
787
+
788
+ /**
789
+ * Builds an array of pairs from the specified range of keys, sorted by key.
790
+ * Each returned pair is also an array: pair[0] is the key, pair[1] is the value.
791
+ * @param low The first key in the array will be greater than or equal to `low`.
792
+ * @param high This method returns when a key larger than this is reached.
793
+ * @param includeHigh If the `high` key is present, its pair will be included
794
+ * in the output if and only if this parameter is true. Note: if the
795
+ * `low` key is present, it is always included in the output.
796
+ * @param maxLength Length limit. getRange will stop scanning the tree when
797
+ * the array reaches this size.
798
+ * @description Computational complexity: O(result.length + log size)
799
+ */
800
+ keysRange(low: K, high: K, includeHigh?: boolean, maxLength: number = 0x3ffffff): Array<[K, V]> {
801
+ const results: Array<[K, V]> = []
802
+ this.root.forRange(low, high, includeHigh, false, this, 0, (k, v) => {
803
+ results.push([k, v as V])
804
+ return results.length > maxLength ? Break : undefined
805
+ })
806
+ return results
807
+ }
808
+
809
+ /** Adds all pairs from a list of key-value pairs.
810
+ * @param entries Pairs to add to this tree. If there are duplicate keys,
811
+ * later pairs currently overwrite earlier ones (e.g. [[0,1],[0,7]]
812
+ * associates 0 with 7.)
813
+ * @returns The number of pairs added to the collection.
814
+ * @description Computational complexity: O(pairs.length * log(size + pairs.length))
815
+ */
816
+ extend(entries: Iterable<[K, V]>): number {
817
+ let added = 0
818
+ for (const pair of entries) {
819
+ this.set(pair[0], pair[1] ?? (null as V))
820
+ added++
821
+ }
822
+ return added
823
+ }
824
+
825
+ /**
826
+ * Scans the specified range of keys, in ascending order by key.
827
+ * Note: the callback `onFound` must not insert or remove items in the
828
+ * collection. Doing so may cause incorrect data to be sent to the
829
+ * callback afterward.
830
+ * @param low The first key scanned will be greater than or equal to `low`.
831
+ * @param high Scanning stops when a key larger than this is reached.
832
+ * @param includeHigh If the `high` key is present, `onFound` is called for
833
+ * that final pair if and only if this parameter is true.
834
+ * @param onFound A function that is called for each key-value pair. This
835
+ * function can return {break:R} to stop early with result R.
836
+ * @param initialCounter Initial third argument of onFound. This value
837
+ * increases by one each time `onFound` is called. Default: 0
838
+ * @returns The number of values found, or R if the callback returned
839
+ * `{break:R}` to stop early.
840
+ * @description Computational complexity: O(number of items scanned + log size)
841
+ */
842
+ rangeForEach<R = number>(
843
+ low: K,
844
+ high: K,
845
+ includeHigh: boolean,
846
+ onFound?: Iteratee<K, V>,
847
+ initialCounter?: number
848
+ ): R | number {
849
+ const r = this.root.forRange(low, high, includeHigh, false, this, initialCounter || 0, onFound)
850
+ return typeof r === 'number' ? r : r.break
851
+ }
852
+
853
+ /**
854
+ * Scans and potentially modifies values for a subsequence of keys.
855
+ * Note: the callback `onFound` should ideally be a pure function.
856
+ * Specifically, it must not insert items, call clone(), or change
857
+ * the collection except via return value; out-of-band editing may
858
+ * cause an exception or may cause incorrect data to be sent to
859
+ * the callback (duplicate or missed items). It must not cause a
860
+ * clone() of the collection, otherwise the clone could be modified
861
+ * by changes requested by the callback.
862
+ * @param low The first key scanned will be greater than or equal to `low`.
863
+ * @param high Scanning stops when a key larger than this is reached.
864
+ * @param includeHigh If the `high` key is present, `onFound` is called for
865
+ * that final pair if and only if this parameter is true.
866
+ * @param onFound A function that is called for each key-value pair. This
867
+ * function can return `{value:v}` to change the value associated
868
+ * with the current key, `{delete:true}` to delete the current pair,
869
+ * `{break:R}` to stop early with result R, or it can return nothing
870
+ * (undefined or {}) to cause no effect and continue iterating.
871
+ * `{break:R}` can be combined with one of the other two commands.
872
+ * The third argument `counter` is the number of items iterated
873
+ * previously; it equals 0 when `onFound` is called the first time.
874
+ * @returns The number of values scanned, or R if the callback returned
875
+ * `{break:R}` to stop early.
876
+ * @description
877
+ * Computational complexity: O(number of items scanned + log size)
878
+ * Note: if the tree has been cloned with clone(), any shared
879
+ * nodes are copied before `onFound` is called. This takes O(n) time
880
+ * where n is proportional to the amount of shared data scanned.
881
+ */
882
+ rangeUpdate<R = V>(
883
+ low: K,
884
+ high: K,
885
+ includeHigh: boolean,
886
+ onFound: (k: K, v: V | undefined, counter: number) => any,
887
+ initialCounter?: number
888
+ ): R | number {
889
+ let root = this.root
890
+ if (root.isShared) this.root = root = root.clone()
891
+ try {
892
+ const r = root.forRange(low, high, includeHigh, true, this, initialCounter || 0, onFound)
893
+ return typeof r === 'number' ? r : r.break
894
+ } finally {
895
+ let isShared
896
+ while (root.keys.length <= 1 && !root.isLeaf) {
897
+ isShared ||= root.isShared
898
+ this.root = root =
899
+ root.keys.length === 0 ? EmptyLeaf : (root as any as BNodeInternal<K, V>).children[0]
900
+ }
901
+ // If any ancestor of the new root was shared, the new root must also be shared
902
+ if (isShared) {
903
+ root.isShared = true
904
+ }
905
+ }
906
+ }
907
+
908
+ /**
909
+ * Removes a range of key-value pairs from the B+ tree.
910
+ * @param low The first key scanned will be greater than or equal to `low`.
911
+ * @param high Scanning stops when a key larger than this is reached.
912
+ * @param includeHigh Specifies whether the `high` key, if present, is deleted.
913
+ * @returns The number of key-value pairs that were deleted.
914
+ * @description Computational complexity: O(log size + number of items deleted)
915
+ */
916
+ removeRange(low: K, high: K, includeHigh: boolean): number {
917
+ return this.rangeUpdate(low, high, includeHigh, DeleteRange)
918
+ }
919
+
920
+ /** Removes a series of keys from the collection. */
921
+ removeKeys(keys: K[]): number {
922
+ let r = 0
923
+ for (let i = 0; i < keys.length; i++) if (this.remove(keys[i])) r++
924
+ return r
925
+ }
926
+
927
+ /** Gets the height of the tree: the number of internal nodes between the
928
+ * BTree object and its leaf nodes (zero if there are no internal nodes). */
929
+ get height(): number {
930
+ let node = this.root as any
931
+ let height = -1
932
+ while (node) {
933
+ height++
934
+ node = node.isLeaf ? undefined : node?.children?.[0]
935
+ }
936
+ return height
937
+ }
938
+
939
+ /** Makes the object read-only to ensure it is not accidentally modified.
940
+ * Freezing does not have to be permanent; unfreeze() reverses the effect.
941
+ * This is accomplished by replacing mutator functions with a function
942
+ * that throws an Error. Compared to using a property (e.g. this.isFrozen)
943
+ * this implementation gives better performance in non-frozen BTrees.
944
+ */
945
+ freeze() {
946
+ const t = this as any
947
+ // Note: all other mutators ultimately call set() or editRange()
948
+ // so we don't need to override those others.
949
+ t.clear =
950
+ t.set =
951
+ t.editRange =
952
+ () => {
953
+ throw TypeError('Attempted to modify a frozen BTree')
954
+ }
955
+ }
956
+
957
+ [Symbol.iterator]() {
958
+ return this.entries()
959
+ }
960
+ }
961
+
962
+ function iterator<T>(
963
+ next: () => IteratorResult<T> = () => ({ done: true, value: undefined })
964
+ ): IterableIterator<T> {
965
+ return {
966
+ next,
967
+ [Symbol.iterator]() {
968
+ return this
969
+ }
970
+ }
971
+ }
972
+
973
+ /** Internal node (non-leaf node) ********************************************/
974
+ class BNodeInternal<K, V> extends BNode<K, V> {
975
+ // Note: conventionally B+ trees have one fewer key than the number of
976
+ // children, but I find it easier to keep the array lengths equal: each
977
+ // keys[i] caches the value of children[i].maxKey().
978
+ children: Array<BNode<K, V>>
979
+
980
+ /**
981
+ * This does not mark `children` as shared, so it is the responsibility of the caller
982
+ * to ensure children are either marked shared, or aren't included in another tree.
983
+ */
984
+ constructor(children: Array<BNode<K, V>>, keys?: K[]) {
985
+ if (!keys) {
986
+ keys = []
987
+ for (let i = 0; i < children.length; i++) keys[i] = children[i].maxKey()
988
+ }
989
+ super(keys)
990
+ this.children = children
991
+ }
992
+
993
+ clone(): BNode<K, V> {
994
+ const children = this.children.slice(0)
995
+ for (let i = 0; i < children.length; i++) children[i].isShared = true
996
+ return new BNodeInternal<K, V>(children, this.keys.slice(0))
997
+ }
998
+
999
+ greedyClone(force?: boolean): BNode<K, V> {
1000
+ if (this.isShared && !force) return this
1001
+ const nu = new BNodeInternal<K, V>(this.children.slice(0), this.keys.slice(0))
1002
+ for (let i = 0; i < nu.children.length; i++) nu.children[i] = nu.children[i].greedyClone(force)
1003
+ return nu
1004
+ }
1005
+
1006
+ minKey() {
1007
+ return this.children[0].minKey()
1008
+ }
1009
+
1010
+ minPair(reusedArray: [K, V]): [K, V] | undefined {
1011
+ return this.children[0].minPair(reusedArray)
1012
+ }
1013
+
1014
+ maxPair(reusedArray: [K, V]): [K, V] | undefined {
1015
+ return this.children[this.children.length - 1].maxPair(reusedArray)
1016
+ }
1017
+
1018
+ get(key: K, defaultValue: V | undefined, tree: BTree<K, V>): V | undefined {
1019
+ const i = this.indexOf(key, 0, tree._compare)
1020
+ const children = this.children
1021
+ return i < children.length ? children[i].get(key, defaultValue, tree) : undefined
1022
+ }
1023
+
1024
+ getPairOrNextLower(
1025
+ key: K,
1026
+ compare: (a: K, b: K) => number,
1027
+ inclusive: boolean,
1028
+ reusedArray: [K, V]
1029
+ ): [K, V] | undefined {
1030
+ const i = this.indexOf(key, 0, compare)
1031
+ const children = this.children
1032
+ if (i >= children.length) return this.maxPair(reusedArray)
1033
+ const result = children[i].getPairOrNextLower(key, compare, inclusive, reusedArray)
1034
+ if (result === undefined && i > 0) {
1035
+ return children[i - 1].maxPair(reusedArray)
1036
+ }
1037
+ return result
1038
+ }
1039
+
1040
+ getPairOrNextHigher(
1041
+ key: K,
1042
+ compare: (a: K, b: K) => number,
1043
+ inclusive: boolean,
1044
+ reusedArray: [K, V]
1045
+ ): [K, V] | undefined {
1046
+ const i = this.indexOf(key, 0, compare)
1047
+ const children = this.children
1048
+ const length = children.length
1049
+ if (i >= length) return undefined
1050
+ const result = children[i].getPairOrNextHigher(key, compare, inclusive, reusedArray)
1051
+ if (result === undefined && i < length - 1) {
1052
+ return children[i + 1].minPair(reusedArray)
1053
+ }
1054
+ return result
1055
+ }
1056
+
1057
+ checkValid(depth: number, tree: BTree<K, V>, baseIndex: number): number {
1058
+ const kL = this.keys.length
1059
+ const cL = this.children.length
1060
+ assert(
1061
+ kL === cL,
1062
+ 'keys/children length mismatch: depth',
1063
+ depth,
1064
+ 'lengths',
1065
+ kL,
1066
+ cL,
1067
+ 'baseIndex',
1068
+ baseIndex
1069
+ )
1070
+ assert(
1071
+ kL > 1 || depth > 0,
1072
+ 'internal node has length',
1073
+ kL,
1074
+ 'at depth',
1075
+ depth,
1076
+ 'baseIndex',
1077
+ baseIndex
1078
+ )
1079
+ let size = 0
1080
+ const c = this.children
1081
+ const k = this.keys
1082
+ let childSize = 0
1083
+ for (let i = 0; i < cL; i++) {
1084
+ size += c[i].checkValid(depth + 1, tree, baseIndex + size)
1085
+ childSize += c[i].keys.length
1086
+ assert(size >= childSize, 'wtf', baseIndex) // no way this will ever fail
1087
+ assert(
1088
+ i === 0 || c[i - 1].constructor === c[i].constructor,
1089
+ 'type mismatch, baseIndex:',
1090
+ baseIndex
1091
+ )
1092
+ if (c[i].maxKey() !== k[i])
1093
+ assert(
1094
+ false,
1095
+ 'keys[',
1096
+ i,
1097
+ '] =',
1098
+ k[i],
1099
+ 'is wrong, should be ',
1100
+ c[i].maxKey(),
1101
+ 'at depth',
1102
+ depth,
1103
+ 'baseIndex',
1104
+ baseIndex
1105
+ )
1106
+ if (!(i === 0 || tree._compare(k[i - 1], k[i]) < 0))
1107
+ assert(false, 'sort violation at depth', depth, 'index', i, 'keys', k[i - 1], k[i])
1108
+ }
1109
+ // 2020/08: BTree doesn't always avoid grossly undersized nodes,
1110
+ // but AFAIK such nodes are pretty harmless, so accept them.
1111
+ const toofew = childSize === 0 // childSize < (tree.maxNodeSize >> 1)*cL;
1112
+ if (toofew || childSize > tree.maxNodeSize * cL)
1113
+ assert(
1114
+ false,
1115
+ toofew ? 'too few' : 'too many',
1116
+ 'children (',
1117
+ childSize,
1118
+ size,
1119
+ ') at depth',
1120
+ depth,
1121
+ 'maxNodeSize:',
1122
+ tree.maxNodeSize,
1123
+ 'children.length:',
1124
+ cL,
1125
+ 'baseIndex:',
1126
+ baseIndex
1127
+ )
1128
+ return size
1129
+ }
1130
+
1131
+ /// //////////////////////////////////////////////////////////////////////////
1132
+ // Internal Node: set & node splitting //////////////////////////////////////
1133
+
1134
+ set(
1135
+ key: K,
1136
+ value: V,
1137
+ overwrite: boolean | undefined,
1138
+ tree: BTree<K, V>
1139
+ ): boolean | BNodeInternal<K, V> {
1140
+ const c = this.children
1141
+ const max = tree._maxNodeSize
1142
+ const cmp = tree._compare
1143
+ let i = Math.min(this.indexOf(key, 0, cmp), c.length - 1)
1144
+ let child = c[i]
1145
+
1146
+ if (child.isShared) c[i] = child = child.clone()
1147
+ if (child.keys.length >= max) {
1148
+ // child is full; inserting anything else will cause a split.
1149
+ // Shifting an item to the left or right sibling may avoid a split.
1150
+ // We can do a shift if the adjacent node is not full and if the
1151
+ // current key can still be placed in the same node after the shift.
1152
+ let other: BNode<K, V>
1153
+ if (i > 0 && (other = c[i - 1]).keys.length < max && cmp(child.keys[0], key) < 0) {
1154
+ if (other.isShared) c[i - 1] = other = other.clone()
1155
+ other.takeFromRight(child)
1156
+ this.keys[i - 1] = other.maxKey()
1157
+ } else if (
1158
+ (other = c[i + 1]) !== undefined &&
1159
+ other.keys.length < max &&
1160
+ cmp(child.maxKey(), key) < 0
1161
+ ) {
1162
+ if (other.isShared) c[i + 1] = other = other.clone()
1163
+ other.takeFromLeft(child)
1164
+ this.keys[i] = c[i].maxKey()
1165
+ }
1166
+ }
1167
+
1168
+ const result = child.set(key, value, overwrite, tree)
1169
+ if (result === false) return false
1170
+ this.keys[i] = child.maxKey()
1171
+ if (result === true) return true
1172
+
1173
+ // The child has split and `result` is a new right child... does it fit?
1174
+ if (this.keys.length < max) {
1175
+ // yes
1176
+ this.insert(i + 1, result)
1177
+ return true
1178
+ } else {
1179
+ // no, we must split also
1180
+ const newRightSibling = this.splitOffRightSide()
1181
+ let target = this as any
1182
+ if (cmp(result.maxKey(), this.maxKey()) > 0) {
1183
+ target = newRightSibling
1184
+ i -= this.keys.length
1185
+ }
1186
+ target.insert(i + 1, result)
1187
+ return newRightSibling
1188
+ }
1189
+ }
1190
+
1191
+ /**
1192
+ * Inserts `child` at index `i`.
1193
+ * This does not mark `child` as shared, so it is the responsibility of the caller
1194
+ * to ensure that either child is marked shared, or it is not included in another tree.
1195
+ */
1196
+ insert(i, child: BNode<K, V>) {
1197
+ this.children.splice(i, 0, child)
1198
+ this.keys.splice(i, 0, child.maxKey())
1199
+ }
1200
+
1201
+ /**
1202
+ * Split this node.
1203
+ * Modifies this to remove the second half of the items, returning a separate node containing them.
1204
+ */
1205
+ splitOffRightSide() {
1206
+ // assert !this.isShared;
1207
+ const half = this.children.length >> 1
1208
+ return new BNodeInternal<K, V>(this.children.splice(half), this.keys.splice(half))
1209
+ }
1210
+
1211
+ takeFromRight(rhs: BNode<K, V>) {
1212
+ // Reminder: parent node must update its copy of key for this node
1213
+ // assert: neither node is shared
1214
+ // assert rhs.keys.length > (maxNodeSize/2 && this.keys.length<maxNodeSize)
1215
+ // eslint-disable-next-line
1216
+ this.keys.push(rhs.keys.shift()!)
1217
+ // eslint-disable-next-line
1218
+ this.children.push((rhs as BNodeInternal<K, V>).children.shift()!)
1219
+ }
1220
+
1221
+ takeFromLeft(lhs: BNode<K, V>) {
1222
+ // Reminder: parent node must update its copy of key for this node
1223
+ // assert: neither node is shared
1224
+ // assert rhs.keys.length > (maxNodeSize/2 && this.keys.length<maxNodeSize)
1225
+ // eslint-disable-next-line
1226
+ this.keys.unshift(lhs.keys.pop()!)
1227
+ // eslint-disable-next-line
1228
+ this.children.unshift((lhs as BNodeInternal<K, V>).children.pop()!)
1229
+ }
1230
+
1231
+ /// //////////////////////////////////////////////////////////////////////////
1232
+ // Internal Node: scanning & deletions //////////////////////////////////////
1233
+
1234
+ // Note: `count` is the next value of the third argument to `onFound`.
1235
+ // A leaf node's `forRange` function returns a new value for this counter,
1236
+ // unless the operation is to stop early.
1237
+ forRange<R>(
1238
+ low: K,
1239
+ high: K,
1240
+ includeHigh: boolean | undefined,
1241
+ editMode: boolean,
1242
+ tree: BTree<K, V>,
1243
+ count: number,
1244
+ onFound?: (k: K, v: V | undefined, counter: number) => any
1245
+ ): any {
1246
+ const cmp = tree._compare
1247
+ const keys = this.keys
1248
+ const children = this.children
1249
+ let iLow = this.indexOf(low, 0, cmp)
1250
+ let i = iLow
1251
+ const iHigh = Math.min(high === low ? iLow : this.indexOf(high, 0, cmp), keys.length - 1)
1252
+ if (!editMode) {
1253
+ // Simple case
1254
+ for (; i <= iHigh; i++) {
1255
+ const result = children[i].forRange(low, high, includeHigh, editMode, tree, count, onFound)
1256
+ if (typeof result !== 'number') return result
1257
+ count = result
1258
+ }
1259
+ } else if (i <= iHigh) {
1260
+ try {
1261
+ for (; i <= iHigh; i++) {
1262
+ if (children[i].isShared) children[i] = children[i].clone()
1263
+ const result = children[i].forRange(
1264
+ low,
1265
+ high,
1266
+ includeHigh,
1267
+ editMode,
1268
+ tree,
1269
+ count,
1270
+ onFound
1271
+ )
1272
+ // Note: if children[i] is empty then keys[i]=undefined.
1273
+ // This is an invalid state, but it is fixed below.
1274
+ keys[i] = children[i].maxKey()
1275
+ if (typeof result !== 'number') return result
1276
+ count = result
1277
+ }
1278
+ } finally {
1279
+ // Deletions may have occurred, so look for opportunities to merge nodes.
1280
+ const half = tree._maxNodeSize >> 1
1281
+ if (iLow > 0) iLow--
1282
+ for (i = iHigh; i >= iLow; i--) {
1283
+ if (children[i].keys.length <= half) {
1284
+ if (children[i].keys.length !== 0) {
1285
+ this.tryMerge(i, tree._maxNodeSize)
1286
+ } else {
1287
+ // child is empty! delete it!
1288
+ keys.splice(i, 1)
1289
+ children.splice(i, 1)
1290
+ }
1291
+ }
1292
+ }
1293
+ if (children.length !== 0 && children[0].keys.length === 0) assert(false, 'emptiness bug')
1294
+ }
1295
+ }
1296
+ return count
1297
+ }
1298
+
1299
+ /** Merges child i with child i+1 if their combined size is not too large */
1300
+ tryMerge(i, maxSize: number): boolean {
1301
+ const children = this.children
1302
+ if (i >= 0 && i + 1 < children.length) {
1303
+ if (children[i].keys.length + children[i + 1].keys.length <= maxSize) {
1304
+ if (children[i].isShared)
1305
+ // cloned already UNLESS i is outside scan range
1306
+ children[i] = children[i].clone()
1307
+ children[i].mergeSibling(children[i + 1], maxSize)
1308
+ children.splice(i + 1, 1)
1309
+ this.keys.splice(i + 1, 1)
1310
+ this.keys[i] = children[i].maxKey()
1311
+ return true
1312
+ }
1313
+ }
1314
+ return false
1315
+ }
1316
+
1317
+ /**
1318
+ * Move children from `rhs` into this.
1319
+ * `rhs` must be part of this tree, and be removed from it after this call
1320
+ * (otherwise isShared for its children could be incorrect).
1321
+ */
1322
+ mergeSibling(rhs: BNode<K, V>, maxNodeSize: number) {
1323
+ // assert !this.isShared;
1324
+ const oldLength = this.keys.length
1325
+ this.keys.push.apply(this.keys, rhs.keys)
1326
+ const rhsChildren = (rhs as any as BNodeInternal<K, V>).children
1327
+ this.children.push.apply(this.children, rhsChildren)
1328
+
1329
+ if (rhs.isShared && !this.isShared) {
1330
+ // All children of a shared node are implicitly shared, and since their new
1331
+ // parent is not shared, they must now be explicitly marked as shared.
1332
+ for (let i = 0; i < rhsChildren.length; i++) rhsChildren[i].isShared = true
1333
+ }
1334
+
1335
+ // If our children are themselves almost empty due to a mass-delete,
1336
+ // they may need to be merged too (but only the oldLength-1 and its
1337
+ // right sibling should need this).
1338
+ this.tryMerge(oldLength - 1, maxNodeSize)
1339
+ }
1340
+ }