@tanstack/db-ivm 0.1.13 → 0.1.15

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 (126) hide show
  1. package/README.md +3 -3
  2. package/dist/cjs/d2.cjs.map +1 -1
  3. package/dist/cjs/graph.cjs.map +1 -1
  4. package/dist/cjs/hashing/hash.cjs.map +1 -1
  5. package/dist/cjs/index.cjs +6 -3
  6. package/dist/cjs/index.cjs.map +1 -1
  7. package/dist/cjs/index.d.cts +1 -0
  8. package/dist/cjs/indexes.cjs +1 -1
  9. package/dist/cjs/indexes.cjs.map +1 -1
  10. package/dist/cjs/multiset.cjs.map +1 -1
  11. package/dist/cjs/operators/concat.cjs.map +1 -1
  12. package/dist/cjs/operators/consolidate.cjs.map +1 -1
  13. package/dist/cjs/operators/count.cjs.map +1 -1
  14. package/dist/cjs/operators/debug.cjs.map +1 -1
  15. package/dist/cjs/operators/distinct.cjs.map +1 -1
  16. package/dist/cjs/operators/filter.cjs.map +1 -1
  17. package/dist/cjs/operators/filterBy.cjs.map +1 -1
  18. package/dist/cjs/operators/groupBy.cjs +2 -1
  19. package/dist/cjs/operators/groupBy.cjs.map +1 -1
  20. package/dist/cjs/operators/groupedTopKWithFractionalIndex.cjs +116 -0
  21. package/dist/cjs/operators/groupedTopKWithFractionalIndex.cjs.map +1 -0
  22. package/dist/cjs/operators/groupedTopKWithFractionalIndex.d.cts +50 -0
  23. package/dist/cjs/operators/index.d.cts +1 -0
  24. package/dist/cjs/operators/join.cjs.map +1 -1
  25. package/dist/cjs/operators/keying.cjs.map +1 -1
  26. package/dist/cjs/operators/map.cjs.map +1 -1
  27. package/dist/cjs/operators/negate.cjs.map +1 -1
  28. package/dist/cjs/operators/orderBy.cjs.map +1 -1
  29. package/dist/cjs/operators/output.cjs.map +1 -1
  30. package/dist/cjs/operators/pipe.cjs.map +1 -1
  31. package/dist/cjs/operators/reduce.cjs.map +1 -1
  32. package/dist/cjs/operators/tap.cjs.map +1 -1
  33. package/dist/cjs/operators/topK.cjs.map +1 -1
  34. package/dist/cjs/operators/topKArray.cjs +137 -0
  35. package/dist/cjs/operators/topKArray.cjs.map +1 -0
  36. package/dist/cjs/operators/topKArray.d.cts +59 -0
  37. package/dist/cjs/operators/topKState.cjs +73 -0
  38. package/dist/cjs/operators/topKState.cjs.map +1 -0
  39. package/dist/cjs/operators/topKState.d.cts +35 -0
  40. package/dist/cjs/operators/topKWithFractionalIndex.cjs +17 -208
  41. package/dist/cjs/operators/topKWithFractionalIndex.cjs.map +1 -1
  42. package/dist/cjs/operators/topKWithFractionalIndex.d.cts +4 -43
  43. package/dist/cjs/operators/topKWithFractionalIndexBTree.d.cts +5 -4
  44. package/dist/cjs/utils.cjs +21 -0
  45. package/dist/cjs/utils.cjs.map +1 -1
  46. package/dist/cjs/utils.d.cts +11 -0
  47. package/dist/esm/d2.js.map +1 -1
  48. package/dist/esm/graph.js.map +1 -1
  49. package/dist/esm/hashing/hash.js.map +1 -1
  50. package/dist/esm/index.d.ts +1 -0
  51. package/dist/esm/index.js +7 -4
  52. package/dist/esm/index.js.map +1 -1
  53. package/dist/esm/indexes.js +1 -1
  54. package/dist/esm/indexes.js.map +1 -1
  55. package/dist/esm/multiset.js.map +1 -1
  56. package/dist/esm/operators/concat.js.map +1 -1
  57. package/dist/esm/operators/consolidate.js.map +1 -1
  58. package/dist/esm/operators/count.js.map +1 -1
  59. package/dist/esm/operators/debug.js.map +1 -1
  60. package/dist/esm/operators/distinct.js.map +1 -1
  61. package/dist/esm/operators/filter.js.map +1 -1
  62. package/dist/esm/operators/filterBy.js.map +1 -1
  63. package/dist/esm/operators/groupBy.js +2 -1
  64. package/dist/esm/operators/groupBy.js.map +1 -1
  65. package/dist/esm/operators/groupedTopKWithFractionalIndex.d.ts +50 -0
  66. package/dist/esm/operators/groupedTopKWithFractionalIndex.js +116 -0
  67. package/dist/esm/operators/groupedTopKWithFractionalIndex.js.map +1 -0
  68. package/dist/esm/operators/index.d.ts +1 -0
  69. package/dist/esm/operators/join.js.map +1 -1
  70. package/dist/esm/operators/keying.js.map +1 -1
  71. package/dist/esm/operators/map.js.map +1 -1
  72. package/dist/esm/operators/negate.js.map +1 -1
  73. package/dist/esm/operators/orderBy.js.map +1 -1
  74. package/dist/esm/operators/output.js.map +1 -1
  75. package/dist/esm/operators/pipe.js.map +1 -1
  76. package/dist/esm/operators/reduce.js.map +1 -1
  77. package/dist/esm/operators/tap.js.map +1 -1
  78. package/dist/esm/operators/topK.js.map +1 -1
  79. package/dist/esm/operators/topKArray.d.ts +59 -0
  80. package/dist/esm/operators/topKArray.js +137 -0
  81. package/dist/esm/operators/topKArray.js.map +1 -0
  82. package/dist/esm/operators/topKState.d.ts +35 -0
  83. package/dist/esm/operators/topKState.js +73 -0
  84. package/dist/esm/operators/topKState.js.map +1 -0
  85. package/dist/esm/operators/topKWithFractionalIndex.d.ts +4 -43
  86. package/dist/esm/operators/topKWithFractionalIndex.js +16 -207
  87. package/dist/esm/operators/topKWithFractionalIndex.js.map +1 -1
  88. package/dist/esm/operators/topKWithFractionalIndexBTree.d.ts +5 -4
  89. package/dist/esm/utils.d.ts +11 -0
  90. package/dist/esm/utils.js +22 -1
  91. package/dist/esm/utils.js.map +1 -1
  92. package/package.json +29 -29
  93. package/src/d2.ts +4 -4
  94. package/src/graph.ts +6 -6
  95. package/src/hashing/hash.ts +3 -3
  96. package/src/hashing/index.ts +2 -2
  97. package/src/index.ts +5 -4
  98. package/src/indexes.ts +8 -8
  99. package/src/multiset.ts +5 -5
  100. package/src/operators/concat.ts +6 -6
  101. package/src/operators/consolidate.ts +6 -6
  102. package/src/operators/count.ts +9 -9
  103. package/src/operators/debug.ts +8 -8
  104. package/src/operators/distinct.ts +11 -11
  105. package/src/operators/filter.ts +8 -8
  106. package/src/operators/filterBy.ts +7 -7
  107. package/src/operators/groupBy.ts +24 -23
  108. package/src/operators/groupedTopKWithFractionalIndex.ts +192 -0
  109. package/src/operators/index.ts +20 -19
  110. package/src/operators/join.ts +23 -23
  111. package/src/operators/keying.ts +4 -4
  112. package/src/operators/map.ts +8 -8
  113. package/src/operators/negate.ts +6 -6
  114. package/src/operators/orderBy.ts +26 -26
  115. package/src/operators/orderByBTree.ts +7 -7
  116. package/src/operators/output.ts +9 -9
  117. package/src/operators/pipe.ts +1 -1
  118. package/src/operators/reduce.ts +9 -9
  119. package/src/operators/tap.ts +8 -8
  120. package/src/operators/topK.ts +8 -8
  121. package/src/operators/topKArray.ts +255 -0
  122. package/src/operators/topKState.ts +111 -0
  123. package/src/operators/topKWithFractionalIndex.ts +36 -381
  124. package/src/operators/topKWithFractionalIndexBTree.ts +21 -30
  125. package/src/types.ts +22 -22
  126. package/src/utils.ts +36 -4
@@ -1,290 +1,57 @@
1
- import { generateKeyBetween } from "fractional-indexing"
2
- import { DifferenceStreamWriter, UnaryOperator } from "../graph.js"
3
- import { StreamBuilder } from "../d2.js"
4
- import { MultiSet } from "../multiset.js"
5
- import {
6
- binarySearch,
7
- diffHalfOpen,
8
- globalObjectIdGenerator,
9
- } from "../utils.js"
10
- import type { HRange } from "../utils.js"
11
- import type { DifferenceStreamReader } from "../graph.js"
12
- import type { IStreamBuilder, PipedOperator } from "../types.js"
1
+ import { DifferenceStreamWriter, UnaryOperator } from '../graph.js'
2
+ import { StreamBuilder } from '../d2.js'
3
+ import { MultiSet } from '../multiset.js'
4
+ import { TopKState, handleMoveIn, handleMoveOut } from './topKState.js'
5
+ import { TopKArray, createKeyedComparator } from './topKArray.js'
6
+ import type { IndexedValue, TopK } from './topKArray.js'
7
+ import type { DifferenceStreamReader } from '../graph.js'
8
+ import type { IStreamBuilder, PipedOperator } from '../types.js'
13
9
 
14
10
  export interface TopKWithFractionalIndexOptions {
15
11
  limit?: number
16
12
  offset?: number
17
13
  setSizeCallback?: (getSize: () => number) => void
18
14
  setWindowFn?: (
19
- windowFn: (options: { offset?: number; limit?: number }) => void
15
+ windowFn: (options: { offset?: number; limit?: number }) => void,
20
16
  ) => void
21
17
  }
22
18
 
23
- export type TopKChanges<V> = {
24
- /** Indicates which element moves into the topK (if any) */
25
- moveIn: IndexedValue<V> | null
26
- /** Indicates which element moves out of the topK (if any) */
27
- moveOut: IndexedValue<V> | null
28
- }
29
-
30
- export type TopKMoveChanges<V> = {
31
- /** Flag that marks whether there were any changes to the topK */
32
- changes: boolean
33
- /** Indicates which elements move into the topK (if any) */
34
- moveIns: Array<IndexedValue<V>>
35
- /** Indicates which elements move out of the topK (if any) */
36
- moveOuts: Array<IndexedValue<V>>
37
- }
38
-
39
- /**
40
- * A topK data structure that supports insertions and deletions
41
- * and returns changes to the topK.
42
- */
43
- export interface TopK<V> {
44
- size: number
45
- insert: (value: V) => TopKChanges<V>
46
- delete: (value: V) => TopKChanges<V>
47
- }
48
-
49
- /**
50
- * Implementation of a topK data structure.
51
- * Uses a sorted array internally to store the values and keeps a topK window over that array.
52
- * Inserts and deletes are O(n) operations because worst case an element is inserted/deleted
53
- * at the start of the array which causes all the elements to shift to the right/left.
54
- */
55
- class TopKArray<V> implements TopK<V> {
56
- #sortedValues: Array<IndexedValue<V>> = []
57
- #comparator: (a: V, b: V) => number
58
- #topKStart: number
59
- #topKEnd: number
60
-
61
- constructor(
62
- offset: number,
63
- limit: number,
64
- comparator: (a: V, b: V) => number
65
- ) {
66
- this.#topKStart = offset
67
- this.#topKEnd = offset + limit
68
- this.#comparator = comparator
69
- }
70
-
71
- get size(): number {
72
- const offset = this.#topKStart
73
- const limit = this.#topKEnd - this.#topKStart
74
- const available = this.#sortedValues.length - offset
75
- return Math.max(0, Math.min(limit, available))
76
- }
77
-
78
- /**
79
- * Moves the topK window
80
- */
81
- move({
82
- offset,
83
- limit,
84
- }: {
85
- offset?: number
86
- limit?: number
87
- }): TopKMoveChanges<V> {
88
- const oldOffset = this.#topKStart
89
- const oldLimit = this.#topKEnd - this.#topKStart
90
-
91
- // `this.#topKEnd` can be `Infinity` if it has no limit
92
- // but `diffHalfOpen` expects a finite range
93
- // so we restrict it to the size of the topK if topKEnd is infinite
94
- const oldRange: HRange = [
95
- this.#topKStart,
96
- this.#topKEnd === Infinity ? this.#topKStart + this.size : this.#topKEnd,
97
- ]
98
-
99
- this.#topKStart = offset ?? oldOffset
100
- this.#topKEnd = this.#topKStart + (limit ?? oldLimit) // can be `Infinity` if limit is `Infinity`
101
-
102
- // Also handle `Infinity` in the newRange
103
- const newRange: HRange = [
104
- this.#topKStart,
105
- this.#topKEnd === Infinity
106
- ? Math.max(this.#topKStart + this.size, oldRange[1]) // since the new limit is Infinity we need to take everything (so we need to take the biggest (finite) topKEnd)
107
- : this.#topKEnd,
108
- ]
109
- const { onlyInA, onlyInB } = diffHalfOpen(oldRange, newRange)
110
-
111
- const moveIns: Array<IndexedValue<V>> = []
112
- onlyInB.forEach((index) => {
113
- const value = this.#sortedValues[index]
114
- if (value) {
115
- moveIns.push(value)
116
- }
117
- })
118
-
119
- const moveOuts: Array<IndexedValue<V>> = []
120
- onlyInA.forEach((index) => {
121
- const value = this.#sortedValues[index]
122
- if (value) {
123
- moveOuts.push(value)
124
- }
125
- })
126
-
127
- // It could be that there are changes (i.e. moveIns or moveOuts)
128
- // but that the collection is lazy so we don't have the data yet that needs to move in/out
129
- // so `moveIns` and `moveOuts` will be empty but `changes` will be true
130
- // this will tell the caller that it needs to run the graph to load more data
131
- return { moveIns, moveOuts, changes: onlyInA.length + onlyInB.length > 0 }
132
- }
133
-
134
- insert(value: V): TopKChanges<V> {
135
- const result: TopKChanges<V> = { moveIn: null, moveOut: null }
136
-
137
- // Lookup insert position
138
- const index = this.#findIndex(value)
139
- // Generate fractional index based on the fractional indices of the elements before and after it
140
- const indexBefore =
141
- index === 0 ? null : getIndex(this.#sortedValues[index - 1]!)
142
- const indexAfter =
143
- index === this.#sortedValues.length
144
- ? null
145
- : getIndex(this.#sortedValues[index]!)
146
- const fractionalIndex = generateKeyBetween(indexBefore, indexAfter)
147
-
148
- // Insert the value at the correct position
149
- const val = indexedValue(value, fractionalIndex)
150
- // Splice is O(n) where n = all elements in the collection (i.e. n >= k) !
151
- this.#sortedValues.splice(index, 0, val)
152
-
153
- // Check if the topK changed
154
- if (index < this.#topKEnd) {
155
- // The inserted element is either before the top K or within the top K
156
- // If it is before the top K then it moves the element that was right before the topK into the topK
157
- // If it is within the top K then the inserted element moves into the top K
158
- // In both cases the last element of the old top K now moves out of the top K
159
- const moveInIndex = Math.max(index, this.#topKStart)
160
- if (moveInIndex < this.#sortedValues.length) {
161
- // We actually have a topK
162
- // because in some cases there may not be enough elements in the array to reach the start of the topK
163
- // e.g. [1, 2, 3] with K = 2 and offset = 3 does not have a topK
164
- result.moveIn = this.#sortedValues[moveInIndex]!
165
-
166
- // We need to remove the element that falls out of the top K
167
- // The element that falls out of the top K has shifted one to the right
168
- // because of the element we inserted, so we find it at index topKEnd
169
- if (this.#topKEnd < this.#sortedValues.length) {
170
- result.moveOut = this.#sortedValues[this.#topKEnd]!
171
- }
172
- }
173
- }
174
-
175
- return result
176
- }
177
-
178
- /**
179
- * Deletes a value that may or may not be in the topK.
180
- * IMPORTANT: this assumes that the value is present in the collection
181
- * if it's not the case it will remove the element
182
- * that is on the position where the provided `value` would be.
183
- */
184
- delete(value: V): TopKChanges<V> {
185
- const result: TopKChanges<V> = { moveIn: null, moveOut: null }
186
-
187
- // Lookup delete position
188
- const index = this.#findIndex(value)
189
- // Remove the value at that position
190
- const [removedElem] = this.#sortedValues.splice(index, 1)
191
-
192
- // Check if the topK changed
193
- if (index < this.#topKEnd) {
194
- // The removed element is either before the top K or within the top K
195
- // If it is before the top K then the first element of the topK moves out of the topK
196
- // If it is within the top K then the removed element moves out of the topK
197
- result.moveOut = removedElem!
198
- if (index < this.#topKStart) {
199
- // The removed element is before the topK
200
- // so actually, the first element of the topK moves out of the topK
201
- // and not the element that we removed
202
- // The first element of the topK is now at index topKStart - 1
203
- // since we removed an element before the topK
204
- const moveOutIndex = this.#topKStart - 1
205
- if (moveOutIndex < this.#sortedValues.length) {
206
- result.moveOut = this.#sortedValues[moveOutIndex]!
207
- } else {
208
- // No value is moving out of the topK
209
- // because there are no elements in the topK
210
- result.moveOut = null
211
- }
212
- }
213
-
214
- // Since we removed an element that was before or in the topK
215
- // the first element after the topK moved one position to the left
216
- // and thus falls into the topK now
217
- const moveInIndex = this.#topKEnd - 1
218
- if (moveInIndex < this.#sortedValues.length) {
219
- result.moveIn = this.#sortedValues[moveInIndex]!
220
- }
221
- }
222
-
223
- return result
224
- }
225
-
226
- // TODO: see if there is a way to refactor the code for insert and delete in the topK above
227
- // because they are very similar, one is shifting the topK window to the left and the other is shifting it to the right
228
- // so i have the feeling there is a common pattern here and we can implement both cases using that pattern
229
-
230
- #findIndex(value: V): number {
231
- return binarySearch(this.#sortedValues, indexedValue(value, ``), (a, b) =>
232
- this.#comparator(getValue(a), getValue(b))
233
- )
234
- }
235
- }
236
-
237
19
  /**
238
20
  * Operator for fractional indexed topK operations
239
21
  * This operator maintains fractional indices for sorted elements
240
22
  * and only updates indices when elements move position
241
23
  */
242
- export class TopKWithFractionalIndexOperator<K, T> extends UnaryOperator<
243
- [K, T],
244
- [K, IndexedValue<T>]
245
- > {
246
- #index: Map<K, number> = new Map() // maps keys to their multiplicity
247
-
248
- /**
249
- * topK data structure that supports insertions and deletions
250
- * and returns changes to the topK.
251
- */
252
- #topK: TopK<TaggedValue<K, T>>
24
+ export class TopKWithFractionalIndexOperator<
25
+ K extends string | number,
26
+ T,
27
+ > extends UnaryOperator<[K, T], [K, IndexedValue<T>]> {
28
+ #state: TopKState<K, T>
253
29
 
254
30
  constructor(
255
31
  id: number,
256
32
  inputA: DifferenceStreamReader<[K, T]>,
257
33
  output: DifferenceStreamWriter<[K, IndexedValue<T>]>,
258
34
  comparator: (a: T, b: T) => number,
259
- options: TopKWithFractionalIndexOptions
35
+ options: TopKWithFractionalIndexOptions,
260
36
  ) {
261
37
  super(id, inputA, output)
262
38
  const limit = options.limit ?? Infinity
263
39
  const offset = options.offset ?? 0
264
- const compareTaggedValues = (
265
- a: TaggedValue<K, T>,
266
- b: TaggedValue<K, T>
267
- ) => {
268
- // First compare on the value
269
- const valueComparison = comparator(getVal(a), getVal(b))
270
- if (valueComparison !== 0) {
271
- return valueComparison
272
- }
273
- // If the values are equal, compare on the tag (object identity)
274
- const tieBreakerA = getTag(a)
275
- const tieBreakerB = getTag(b)
276
- return tieBreakerA - tieBreakerB
277
- }
278
- this.#topK = this.createTopK(offset, limit, compareTaggedValues)
279
- options.setSizeCallback?.(() => this.#topK.size)
40
+ const topK = this.createTopK(
41
+ offset,
42
+ limit,
43
+ createKeyedComparator(comparator),
44
+ )
45
+ this.#state = new TopKState(topK)
46
+ options.setSizeCallback?.(() => this.#state.size)
280
47
  options.setWindowFn?.(this.moveTopK.bind(this))
281
48
  }
282
49
 
283
50
  protected createTopK(
284
51
  offset: number,
285
52
  limit: number,
286
- comparator: (a: TaggedValue<K, T>, b: TaggedValue<K, T>) => number
287
- ): TopK<TaggedValue<K, T>> {
53
+ comparator: (a: [K, T], b: [K, T]) => number,
54
+ ): TopK<[K, T]> {
288
55
  return new TopKArray(offset, limit, comparator)
289
56
  }
290
57
 
@@ -293,18 +60,11 @@ export class TopKWithFractionalIndexOperator<K, T> extends UnaryOperator<
293
60
  * Any changes to the topK are sent to the output.
294
61
  */
295
62
  moveTopK({ offset, limit }: { offset?: number; limit?: number }) {
296
- if (!(this.#topK instanceof TopKArray)) {
297
- throw new Error(
298
- `Cannot move B+-tree implementation of TopK with fractional index`
299
- )
300
- }
301
-
302
63
  const result: Array<[[K, IndexedValue<T>], number]> = []
64
+ const diff = this.#state.move({ offset, limit })
303
65
 
304
- const diff = this.#topK.move({ offset, limit })
305
-
306
- diff.moveIns.forEach((moveIn) => this.handleMoveIn(moveIn, result))
307
- diff.moveOuts.forEach((moveOut) => this.handleMoveOut(moveOut, result))
66
+ diff.moveIns.forEach((moveIn) => handleMoveIn(moveIn, result))
67
+ diff.moveOuts.forEach((moveOut) => handleMoveOut(moveOut, result))
308
68
 
309
69
  if (diff.changes) {
310
70
  // There are changes to the topK
@@ -332,78 +92,11 @@ export class TopKWithFractionalIndexOperator<K, T> extends UnaryOperator<
332
92
  key: K,
333
93
  value: T,
334
94
  multiplicity: number,
335
- result: Array<[[K, IndexedValue<T>], number]>
95
+ result: Array<[[K, IndexedValue<T>], number]>,
336
96
  ): void {
337
- const { oldMultiplicity, newMultiplicity } = this.addKey(key, multiplicity)
338
-
339
- let res: TopKChanges<TaggedValue<K, T>> = {
340
- moveIn: null,
341
- moveOut: null,
342
- }
343
- if (oldMultiplicity <= 0 && newMultiplicity > 0) {
344
- // The value was invisible but should now be visible
345
- // Need to insert it into the array of sorted values
346
- const taggedValue = tagValue(key, value)
347
- res = this.#topK.insert(taggedValue)
348
- } else if (oldMultiplicity > 0 && newMultiplicity <= 0) {
349
- // The value was visible but should now be invisible
350
- // Need to remove it from the array of sorted values
351
- const taggedValue = tagValue(key, value)
352
- res = this.#topK.delete(taggedValue)
353
- } else {
354
- // The value was invisible and it remains invisible
355
- // or it was visible and remains visible
356
- // so it doesn't affect the topK
357
- }
358
-
359
- this.handleMoveIn(res.moveIn, result)
360
- this.handleMoveOut(res.moveOut, result)
361
-
362
- return
363
- }
364
-
365
- private handleMoveIn(
366
- moveIn: IndexedValue<TaggedValue<K, T>> | null,
367
- result: Array<[[K, IndexedValue<T>], number]>
368
- ) {
369
- if (moveIn) {
370
- const index = getIndex(moveIn)
371
- const taggedValue = getValue(moveIn)
372
- const k = getKey(taggedValue)
373
- const val = getVal(taggedValue)
374
- result.push([[k, [val, index]], 1])
375
- }
376
- }
377
-
378
- private handleMoveOut(
379
- moveOut: IndexedValue<TaggedValue<K, T>> | null,
380
- result: Array<[[K, IndexedValue<T>], number]>
381
- ) {
382
- if (moveOut) {
383
- const index = getIndex(moveOut)
384
- const taggedValue = getValue(moveOut)
385
- const k = getKey(taggedValue)
386
- const val = getVal(taggedValue)
387
- result.push([[k, [val, index]], -1])
388
- }
389
- }
390
-
391
- private getMultiplicity(key: K): number {
392
- return this.#index.get(key) ?? 0
393
- }
394
-
395
- private addKey(
396
- key: K,
397
- multiplicity: number
398
- ): { oldMultiplicity: number; newMultiplicity: number } {
399
- const oldMultiplicity = this.getMultiplicity(key)
400
- const newMultiplicity = oldMultiplicity + multiplicity
401
- if (newMultiplicity === 0) {
402
- this.#index.delete(key)
403
- } else {
404
- this.#index.set(key, newMultiplicity)
405
- }
406
- return { oldMultiplicity, newMultiplicity }
97
+ const changes = this.#state.processElement(key, value, multiplicity)
98
+ handleMoveIn(changes.moveIn, result)
99
+ handleMoveOut(changes.moveOut, result)
407
100
  }
408
101
  }
409
102
 
@@ -417,65 +110,27 @@ export class TopKWithFractionalIndexOperator<K, T> extends UnaryOperator<
417
110
  * @param options - An optional object containing limit and offset properties
418
111
  * @returns A piped operator that orders the elements and limits the number of results
419
112
  */
420
- export function topKWithFractionalIndex<KType, T>(
113
+ export function topKWithFractionalIndex<KType extends string | number, T>(
421
114
  comparator: (a: T, b: T) => number,
422
- options?: TopKWithFractionalIndexOptions
115
+ options?: TopKWithFractionalIndexOptions,
423
116
  ): PipedOperator<[KType, T], [KType, IndexedValue<T>]> {
424
117
  const opts = options || {}
425
118
 
426
119
  return (
427
- stream: IStreamBuilder<[KType, T]>
120
+ stream: IStreamBuilder<[KType, T]>,
428
121
  ): IStreamBuilder<[KType, IndexedValue<T>]> => {
429
122
  const output = new StreamBuilder<[KType, IndexedValue<T>]>(
430
123
  stream.graph,
431
- new DifferenceStreamWriter<[KType, IndexedValue<T>]>()
124
+ new DifferenceStreamWriter<[KType, IndexedValue<T>]>(),
432
125
  )
433
126
  const operator = new TopKWithFractionalIndexOperator<KType, T>(
434
127
  stream.graph.getNextOperatorId(),
435
128
  stream.connectReader(),
436
129
  output.writer,
437
130
  comparator,
438
- opts
131
+ opts,
439
132
  )
440
133
  stream.graph.addOperator(operator)
441
134
  return output
442
135
  }
443
136
  }
444
-
445
- // Abstraction for fractionally indexed values
446
- export type FractionalIndex = string
447
- export type IndexedValue<V> = [V, FractionalIndex]
448
-
449
- export function indexedValue<V>(
450
- value: V,
451
- index: FractionalIndex
452
- ): IndexedValue<V> {
453
- return [value, index]
454
- }
455
-
456
- export function getValue<V>(indexedVal: IndexedValue<V>): V {
457
- return indexedVal[0]
458
- }
459
-
460
- export function getIndex<V>(indexedVal: IndexedValue<V>): FractionalIndex {
461
- return indexedVal[1]
462
- }
463
-
464
- export type Tag = number
465
- export type TaggedValue<K, V> = [K, V, Tag]
466
-
467
- function tagValue<K, V>(key: K, value: V): TaggedValue<K, V> {
468
- return [key, value, globalObjectIdGenerator.getId(key)]
469
- }
470
-
471
- function getKey<K, V>(tieBreakerTaggedValue: TaggedValue<K, V>): K {
472
- return tieBreakerTaggedValue[0]
473
- }
474
-
475
- function getVal<K, V>(tieBreakerTaggedValue: TaggedValue<K, V>): V {
476
- return tieBreakerTaggedValue[1]
477
- }
478
-
479
- function getTag<K, V>(tieBreakerTaggedValue: TaggedValue<K, V>): Tag {
480
- return tieBreakerTaggedValue[2]
481
- }
@@ -1,20 +1,11 @@
1
- import { generateKeyBetween } from "fractional-indexing"
2
- import { DifferenceStreamWriter } from "../graph.js"
3
- import { StreamBuilder } from "../d2.js"
4
- import {
5
- TopKWithFractionalIndexOperator,
6
- getIndex,
7
- getValue,
8
- indexedValue,
9
- } from "./topKWithFractionalIndex.js"
10
- import type { IStreamBuilder, PipedOperator } from "../types.js"
11
- import type {
12
- IndexedValue,
13
- TaggedValue,
14
- TopK,
15
- TopKChanges,
16
- TopKWithFractionalIndexOptions,
17
- } from "./topKWithFractionalIndex.js"
1
+ import { generateKeyBetween } from 'fractional-indexing'
2
+ import { DifferenceStreamWriter } from '../graph.js'
3
+ import { StreamBuilder } from '../d2.js'
4
+ import { TopKWithFractionalIndexOperator } from './topKWithFractionalIndex.js'
5
+ import { getIndex, getValue, indexedValue } from './topKArray.js'
6
+ import type { IndexedValue, TopK, TopKChanges } from './topKArray.js'
7
+ import type { IStreamBuilder, PipedOperator } from '../types.js'
8
+ import type { TopKWithFractionalIndexOptions } from './topKWithFractionalIndex.js'
18
9
 
19
10
  interface BTree<Key, Value> {
20
11
  nextLowerPair: (key: Key) => [Key, Value] | undefined
@@ -30,7 +21,7 @@ interface BTreeClass {
30
21
  new <Key, Value>(
31
22
  entries?: Array<[Key, Value]>,
32
23
  compare?: (a: Key, b: Key) => number,
33
- maxNodeSize?: number
24
+ maxNodeSize?: number,
34
25
  ): BTree<Key, Value>
35
26
  }
36
27
 
@@ -61,11 +52,11 @@ class TopKTree<V> implements TopK<V> {
61
52
  constructor(
62
53
  offset: number,
63
54
  limit: number,
64
- comparator: (a: V, b: V) => number
55
+ comparator: (a: V, b: V) => number,
65
56
  ) {
66
57
  if (BTree === undefined) {
67
58
  throw new Error(
68
- `B+ tree not loaded. You need to call loadBTree() before using TopKTree.`
59
+ `B+ tree not loaded. You need to call loadBTree() before using TopKTree.`,
69
60
  )
70
61
  }
71
62
 
@@ -243,17 +234,17 @@ class TopKTree<V> implements TopK<V> {
243
234
  * and only updates indices when elements move position
244
235
  */
245
236
  export class TopKWithFractionalIndexBTreeOperator<
246
- K,
237
+ K extends string | number,
247
238
  T,
248
239
  > extends TopKWithFractionalIndexOperator<K, T> {
249
240
  protected override createTopK(
250
241
  offset: number,
251
242
  limit: number,
252
- comparator: (a: TaggedValue<K, T>, b: TaggedValue<K, T>) => number
253
- ): TopK<TaggedValue<K, T>> {
243
+ comparator: (a: [K, T], b: [K, T]) => number,
244
+ ): TopK<[K, T]> {
254
245
  if (BTree === undefined) {
255
246
  throw new Error(
256
- `B+ tree not loaded. You need to call loadBTree() before using TopKWithFractionalIndexBTreeOperator.`
247
+ `B+ tree not loaded. You need to call loadBTree() before using TopKWithFractionalIndexBTreeOperator.`,
257
248
  )
258
249
  }
259
250
  return new TopKTree(offset, limit, comparator)
@@ -275,31 +266,31 @@ export class TopKWithFractionalIndexBTreeOperator<
275
266
  * @param options - An optional object containing limit and offset properties
276
267
  * @returns A piped operator that orders the elements and limits the number of results
277
268
  */
278
- export function topKWithFractionalIndexBTree<KType, T>(
269
+ export function topKWithFractionalIndexBTree<KType extends string | number, T>(
279
270
  comparator: (a: T, b: T) => number,
280
- options?: TopKWithFractionalIndexOptions
271
+ options?: TopKWithFractionalIndexOptions,
281
272
  ): PipedOperator<[KType, T], [KType, IndexedValue<T>]> {
282
273
  const opts = options || {}
283
274
 
284
275
  if (BTree === undefined) {
285
276
  throw new Error(
286
- `B+ tree not loaded. You need to call loadBTree() before using topKWithFractionalIndexBTree.`
277
+ `B+ tree not loaded. You need to call loadBTree() before using topKWithFractionalIndexBTree.`,
287
278
  )
288
279
  }
289
280
 
290
281
  return (
291
- stream: IStreamBuilder<[KType, T]>
282
+ stream: IStreamBuilder<[KType, T]>,
292
283
  ): IStreamBuilder<[KType, IndexedValue<T>]> => {
293
284
  const output = new StreamBuilder<[KType, IndexedValue<T>]>(
294
285
  stream.graph,
295
- new DifferenceStreamWriter<[KType, IndexedValue<T>]>()
286
+ new DifferenceStreamWriter<[KType, IndexedValue<T>]>(),
296
287
  )
297
288
  const operator = new TopKWithFractionalIndexBTreeOperator<KType, T>(
298
289
  stream.graph.getNextOperatorId(),
299
290
  stream.connectReader(),
300
291
  output.writer,
301
292
  comparator,
302
- opts
293
+ opts,
303
294
  )
304
295
  stream.graph.addOperator(operator)
305
296
  return output