@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.
- package/README.md +3 -3
- package/dist/cjs/d2.cjs.map +1 -1
- package/dist/cjs/graph.cjs.map +1 -1
- package/dist/cjs/hashing/hash.cjs.map +1 -1
- package/dist/cjs/index.cjs +6 -3
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +1 -0
- package/dist/cjs/indexes.cjs +1 -1
- package/dist/cjs/indexes.cjs.map +1 -1
- package/dist/cjs/multiset.cjs.map +1 -1
- package/dist/cjs/operators/concat.cjs.map +1 -1
- package/dist/cjs/operators/consolidate.cjs.map +1 -1
- package/dist/cjs/operators/count.cjs.map +1 -1
- package/dist/cjs/operators/debug.cjs.map +1 -1
- package/dist/cjs/operators/distinct.cjs.map +1 -1
- package/dist/cjs/operators/filter.cjs.map +1 -1
- package/dist/cjs/operators/filterBy.cjs.map +1 -1
- package/dist/cjs/operators/groupBy.cjs +2 -1
- package/dist/cjs/operators/groupBy.cjs.map +1 -1
- package/dist/cjs/operators/groupedTopKWithFractionalIndex.cjs +116 -0
- package/dist/cjs/operators/groupedTopKWithFractionalIndex.cjs.map +1 -0
- package/dist/cjs/operators/groupedTopKWithFractionalIndex.d.cts +50 -0
- package/dist/cjs/operators/index.d.cts +1 -0
- package/dist/cjs/operators/join.cjs.map +1 -1
- package/dist/cjs/operators/keying.cjs.map +1 -1
- package/dist/cjs/operators/map.cjs.map +1 -1
- package/dist/cjs/operators/negate.cjs.map +1 -1
- package/dist/cjs/operators/orderBy.cjs.map +1 -1
- package/dist/cjs/operators/output.cjs.map +1 -1
- package/dist/cjs/operators/pipe.cjs.map +1 -1
- package/dist/cjs/operators/reduce.cjs.map +1 -1
- package/dist/cjs/operators/tap.cjs.map +1 -1
- package/dist/cjs/operators/topK.cjs.map +1 -1
- package/dist/cjs/operators/topKArray.cjs +137 -0
- package/dist/cjs/operators/topKArray.cjs.map +1 -0
- package/dist/cjs/operators/topKArray.d.cts +59 -0
- package/dist/cjs/operators/topKState.cjs +73 -0
- package/dist/cjs/operators/topKState.cjs.map +1 -0
- package/dist/cjs/operators/topKState.d.cts +35 -0
- package/dist/cjs/operators/topKWithFractionalIndex.cjs +17 -208
- package/dist/cjs/operators/topKWithFractionalIndex.cjs.map +1 -1
- package/dist/cjs/operators/topKWithFractionalIndex.d.cts +4 -43
- package/dist/cjs/operators/topKWithFractionalIndexBTree.d.cts +5 -4
- package/dist/cjs/utils.cjs +21 -0
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +11 -0
- package/dist/esm/d2.js.map +1 -1
- package/dist/esm/graph.js.map +1 -1
- package/dist/esm/hashing/hash.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +7 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/indexes.js +1 -1
- package/dist/esm/indexes.js.map +1 -1
- package/dist/esm/multiset.js.map +1 -1
- package/dist/esm/operators/concat.js.map +1 -1
- package/dist/esm/operators/consolidate.js.map +1 -1
- package/dist/esm/operators/count.js.map +1 -1
- package/dist/esm/operators/debug.js.map +1 -1
- package/dist/esm/operators/distinct.js.map +1 -1
- package/dist/esm/operators/filter.js.map +1 -1
- package/dist/esm/operators/filterBy.js.map +1 -1
- package/dist/esm/operators/groupBy.js +2 -1
- package/dist/esm/operators/groupBy.js.map +1 -1
- package/dist/esm/operators/groupedTopKWithFractionalIndex.d.ts +50 -0
- package/dist/esm/operators/groupedTopKWithFractionalIndex.js +116 -0
- package/dist/esm/operators/groupedTopKWithFractionalIndex.js.map +1 -0
- package/dist/esm/operators/index.d.ts +1 -0
- package/dist/esm/operators/join.js.map +1 -1
- package/dist/esm/operators/keying.js.map +1 -1
- package/dist/esm/operators/map.js.map +1 -1
- package/dist/esm/operators/negate.js.map +1 -1
- package/dist/esm/operators/orderBy.js.map +1 -1
- package/dist/esm/operators/output.js.map +1 -1
- package/dist/esm/operators/pipe.js.map +1 -1
- package/dist/esm/operators/reduce.js.map +1 -1
- package/dist/esm/operators/tap.js.map +1 -1
- package/dist/esm/operators/topK.js.map +1 -1
- package/dist/esm/operators/topKArray.d.ts +59 -0
- package/dist/esm/operators/topKArray.js +137 -0
- package/dist/esm/operators/topKArray.js.map +1 -0
- package/dist/esm/operators/topKState.d.ts +35 -0
- package/dist/esm/operators/topKState.js +73 -0
- package/dist/esm/operators/topKState.js.map +1 -0
- package/dist/esm/operators/topKWithFractionalIndex.d.ts +4 -43
- package/dist/esm/operators/topKWithFractionalIndex.js +16 -207
- package/dist/esm/operators/topKWithFractionalIndex.js.map +1 -1
- package/dist/esm/operators/topKWithFractionalIndexBTree.d.ts +5 -4
- package/dist/esm/utils.d.ts +11 -0
- package/dist/esm/utils.js +22 -1
- package/dist/esm/utils.js.map +1 -1
- package/package.json +29 -29
- package/src/d2.ts +4 -4
- package/src/graph.ts +6 -6
- package/src/hashing/hash.ts +3 -3
- package/src/hashing/index.ts +2 -2
- package/src/index.ts +5 -4
- package/src/indexes.ts +8 -8
- package/src/multiset.ts +5 -5
- package/src/operators/concat.ts +6 -6
- package/src/operators/consolidate.ts +6 -6
- package/src/operators/count.ts +9 -9
- package/src/operators/debug.ts +8 -8
- package/src/operators/distinct.ts +11 -11
- package/src/operators/filter.ts +8 -8
- package/src/operators/filterBy.ts +7 -7
- package/src/operators/groupBy.ts +24 -23
- package/src/operators/groupedTopKWithFractionalIndex.ts +192 -0
- package/src/operators/index.ts +20 -19
- package/src/operators/join.ts +23 -23
- package/src/operators/keying.ts +4 -4
- package/src/operators/map.ts +8 -8
- package/src/operators/negate.ts +6 -6
- package/src/operators/orderBy.ts +26 -26
- package/src/operators/orderByBTree.ts +7 -7
- package/src/operators/output.ts +9 -9
- package/src/operators/pipe.ts +1 -1
- package/src/operators/reduce.ts +9 -9
- package/src/operators/tap.ts +8 -8
- package/src/operators/topK.ts +8 -8
- package/src/operators/topKArray.ts +255 -0
- package/src/operators/topKState.ts +111 -0
- package/src/operators/topKWithFractionalIndex.ts +36 -381
- package/src/operators/topKWithFractionalIndexBTree.ts +21 -30
- package/src/types.ts +22 -22
- package/src/utils.ts +36 -4
|
@@ -1,290 +1,57 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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<
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
> {
|
|
246
|
-
#
|
|
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
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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:
|
|
287
|
-
): TopK<
|
|
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
|
-
|
|
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
|
|
338
|
-
|
|
339
|
-
|
|
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
|
|
2
|
-
import { DifferenceStreamWriter } from
|
|
3
|
-
import { StreamBuilder } from
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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:
|
|
253
|
-
): TopK<
|
|
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
|