@tanstack/db-ivm 0.0.0
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 +58 -0
- package/package.json +63 -0
- package/src/d2.ts +150 -0
- package/src/graph.ts +131 -0
- package/src/index.ts +4 -0
- package/src/indexes.ts +117 -0
- package/src/multiset.ts +215 -0
- package/src/operators/concat.ts +45 -0
- package/src/operators/consolidate.ts +50 -0
- package/src/operators/count.ts +52 -0
- package/src/operators/debug.ts +58 -0
- package/src/operators/distinct.ts +98 -0
- package/src/operators/filter.ts +48 -0
- package/src/operators/filterBy.ts +31 -0
- package/src/operators/groupBy.ts +354 -0
- package/src/operators/index.ts +18 -0
- package/src/operators/join.ts +265 -0
- package/src/operators/keying.ts +34 -0
- package/src/operators/map.ts +48 -0
- package/src/operators/negate.ts +33 -0
- package/src/operators/orderBy.ts +213 -0
- package/src/operators/orderByBTree.ts +20 -0
- package/src/operators/output.ts +53 -0
- package/src/operators/pipe.ts +55 -0
- package/src/operators/reduce.ts +124 -0
- package/src/operators/topK.ts +92 -0
- package/src/operators/topKWithFractionalIndex.ts +347 -0
- package/src/operators/topKWithFractionalIndexBTree.ts +306 -0
- package/src/types.ts +376 -0
- package/src/utils.ts +163 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { DifferenceStreamWriter, LinearUnaryOperator } from "../graph.js"
|
|
2
|
+
import { StreamBuilder } from "../d2.js"
|
|
3
|
+
import type { IStreamBuilder, PipedOperator } from "../types.js"
|
|
4
|
+
import type { DifferenceStreamReader } from "../graph.js"
|
|
5
|
+
import type { MultiSet } from "../multiset.js"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Operator that applies a function to each element in the input stream
|
|
9
|
+
*/
|
|
10
|
+
export class MapOperator<T, U> extends LinearUnaryOperator<T, U> {
|
|
11
|
+
#f: (data: T) => U
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
id: number,
|
|
15
|
+
inputA: DifferenceStreamReader<T>,
|
|
16
|
+
output: DifferenceStreamWriter<U>,
|
|
17
|
+
f: (data: T) => U
|
|
18
|
+
) {
|
|
19
|
+
super(id, inputA, output)
|
|
20
|
+
this.#f = f
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
inner(collection: MultiSet<T>): MultiSet<U> {
|
|
24
|
+
return collection.map(this.#f)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Applies a function to each element in the input stream
|
|
30
|
+
* @param f - The function to apply to each element
|
|
31
|
+
*/
|
|
32
|
+
export function map<T, O>(f: (data: T) => O): PipedOperator<T, O> {
|
|
33
|
+
return (stream: IStreamBuilder<T>): IStreamBuilder<O> => {
|
|
34
|
+
const output = new StreamBuilder<O>(
|
|
35
|
+
stream.graph,
|
|
36
|
+
new DifferenceStreamWriter<O>()
|
|
37
|
+
)
|
|
38
|
+
const operator = new MapOperator<T, O>(
|
|
39
|
+
stream.graph.getNextOperatorId(),
|
|
40
|
+
stream.connectReader(),
|
|
41
|
+
output.writer,
|
|
42
|
+
f
|
|
43
|
+
)
|
|
44
|
+
stream.graph.addOperator(operator)
|
|
45
|
+
stream.graph.addStream(output.connectReader())
|
|
46
|
+
return output
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { DifferenceStreamWriter, LinearUnaryOperator } from "../graph.js"
|
|
2
|
+
import { StreamBuilder } from "../d2.js"
|
|
3
|
+
import type { IStreamBuilder, PipedOperator } from "../types.js"
|
|
4
|
+
import type { MultiSet } from "../multiset.js"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Operator that negates the multiplicities in the input stream
|
|
8
|
+
*/
|
|
9
|
+
export class NegateOperator<T> extends LinearUnaryOperator<T, T> {
|
|
10
|
+
inner(collection: MultiSet<T>): MultiSet<T> {
|
|
11
|
+
return collection.negate()
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Negates the multiplicities in the input stream
|
|
17
|
+
*/
|
|
18
|
+
export function negate<T>(): PipedOperator<T, T> {
|
|
19
|
+
return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {
|
|
20
|
+
const output = new StreamBuilder<T>(
|
|
21
|
+
stream.graph,
|
|
22
|
+
new DifferenceStreamWriter<T>()
|
|
23
|
+
)
|
|
24
|
+
const operator = new NegateOperator<T>(
|
|
25
|
+
stream.graph.getNextOperatorId(),
|
|
26
|
+
stream.connectReader(),
|
|
27
|
+
output.writer
|
|
28
|
+
)
|
|
29
|
+
stream.graph.addOperator(operator)
|
|
30
|
+
stream.graph.addStream(output.connectReader())
|
|
31
|
+
return output
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { topK, topKWithIndex } from "./topK.js"
|
|
2
|
+
import { topKWithFractionalIndex } from "./topKWithFractionalIndex.js"
|
|
3
|
+
import { map } from "./map.js"
|
|
4
|
+
import { innerJoin } from "./join.js"
|
|
5
|
+
import { consolidate } from "./consolidate.js"
|
|
6
|
+
import type { KeyValue } from "../types.js"
|
|
7
|
+
import type { IStreamBuilder } from "../types"
|
|
8
|
+
|
|
9
|
+
export interface OrderByOptions<Ve> {
|
|
10
|
+
comparator?: (a: Ve, b: Ve) => number
|
|
11
|
+
limit?: number
|
|
12
|
+
offset?: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Orders the elements and limits the number of results, with optional offset
|
|
17
|
+
* This requires a keyed stream, and uses the `topK` operator to order all the elements.
|
|
18
|
+
*
|
|
19
|
+
* @param valueExtractor - A function that extracts the value to order by from the element
|
|
20
|
+
* @param options - An optional object containing comparator, limit and offset properties
|
|
21
|
+
* @returns A piped operator that orders the elements and limits the number of results
|
|
22
|
+
*/
|
|
23
|
+
export function orderBy<T extends KeyValue<unknown, unknown>, Ve = unknown>(
|
|
24
|
+
valueExtractor: (
|
|
25
|
+
value: T extends KeyValue<unknown, infer V> ? V : never
|
|
26
|
+
) => Ve,
|
|
27
|
+
options?: OrderByOptions<Ve>
|
|
28
|
+
) {
|
|
29
|
+
const limit = options?.limit ?? Infinity
|
|
30
|
+
const offset = options?.offset ?? 0
|
|
31
|
+
const comparator =
|
|
32
|
+
options?.comparator ??
|
|
33
|
+
((a, b) => {
|
|
34
|
+
// Default to JS like ordering
|
|
35
|
+
if (a === b) return 0
|
|
36
|
+
if (a < b) return -1
|
|
37
|
+
return 1
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {
|
|
41
|
+
type KeyType = T extends KeyValue<infer K, unknown> ? K : never
|
|
42
|
+
|
|
43
|
+
return stream.pipe(
|
|
44
|
+
map(
|
|
45
|
+
([key, value]) =>
|
|
46
|
+
[
|
|
47
|
+
null,
|
|
48
|
+
[
|
|
49
|
+
key,
|
|
50
|
+
valueExtractor(
|
|
51
|
+
value as T extends KeyValue<unknown, infer V> ? V : never
|
|
52
|
+
),
|
|
53
|
+
],
|
|
54
|
+
] as KeyValue<null, [KeyType, Ve]>
|
|
55
|
+
),
|
|
56
|
+
topK((a, b) => comparator(a[1], b[1]), { limit, offset }),
|
|
57
|
+
map(([_, [key]]) => [key, null] as KeyValue<KeyType, null>),
|
|
58
|
+
innerJoin(stream),
|
|
59
|
+
map(([key, value]) => {
|
|
60
|
+
return [key, value[1]] as T
|
|
61
|
+
}),
|
|
62
|
+
consolidate()
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Orders the elements and limits the number of results, with optional offset and
|
|
69
|
+
* annotates the value with the index.
|
|
70
|
+
* This requires a keyed stream, and uses the `topKWithIndex` operator to order all the elements.
|
|
71
|
+
*
|
|
72
|
+
* @param valueExtractor - A function that extracts the value to order by from the element
|
|
73
|
+
* @param options - An optional object containing comparator, limit and offset properties
|
|
74
|
+
* @returns A piped operator that orders the elements and limits the number of results
|
|
75
|
+
*/
|
|
76
|
+
export function orderByWithIndex<
|
|
77
|
+
T extends KeyValue<unknown, unknown>,
|
|
78
|
+
Ve = unknown,
|
|
79
|
+
>(
|
|
80
|
+
valueExtractor: (
|
|
81
|
+
value: T extends KeyValue<unknown, infer V> ? V : never
|
|
82
|
+
) => Ve,
|
|
83
|
+
options?: OrderByOptions<Ve>
|
|
84
|
+
) {
|
|
85
|
+
const limit = options?.limit ?? Infinity
|
|
86
|
+
const offset = options?.offset ?? 0
|
|
87
|
+
const comparator =
|
|
88
|
+
options?.comparator ??
|
|
89
|
+
((a, b) => {
|
|
90
|
+
// Default to JS like ordering
|
|
91
|
+
if (a === b) return 0
|
|
92
|
+
if (a < b) return -1
|
|
93
|
+
return 1
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
stream: IStreamBuilder<T>
|
|
98
|
+
): IStreamBuilder<
|
|
99
|
+
KeyValue<
|
|
100
|
+
T extends KeyValue<infer K, unknown> ? K : never,
|
|
101
|
+
[T extends KeyValue<unknown, infer V> ? V : never, number]
|
|
102
|
+
>
|
|
103
|
+
> => {
|
|
104
|
+
type KeyType = T extends KeyValue<infer K, unknown> ? K : never
|
|
105
|
+
type ValueType = T extends KeyValue<unknown, infer V> ? V : never
|
|
106
|
+
|
|
107
|
+
return stream.pipe(
|
|
108
|
+
map(
|
|
109
|
+
([key, value]) =>
|
|
110
|
+
[
|
|
111
|
+
null,
|
|
112
|
+
[
|
|
113
|
+
key,
|
|
114
|
+
valueExtractor(
|
|
115
|
+
value as T extends KeyValue<unknown, infer V> ? V : never
|
|
116
|
+
),
|
|
117
|
+
],
|
|
118
|
+
] as KeyValue<null, [KeyType, Ve]>
|
|
119
|
+
),
|
|
120
|
+
topKWithIndex((a, b) => comparator(a[1], b[1]), { limit, offset }),
|
|
121
|
+
map(([_, [[key], index]]) => [key, index] as KeyValue<KeyType, number>),
|
|
122
|
+
innerJoin(stream),
|
|
123
|
+
map(([key, [index, value]]) => {
|
|
124
|
+
return [key, [value, index]] as KeyValue<KeyType, [ValueType, number]>
|
|
125
|
+
}),
|
|
126
|
+
consolidate()
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function orderByWithFractionalIndexBase<
|
|
132
|
+
T extends KeyValue<unknown, unknown>,
|
|
133
|
+
Ve = unknown,
|
|
134
|
+
>(
|
|
135
|
+
topKFunction: typeof topKWithFractionalIndex,
|
|
136
|
+
valueExtractor: (
|
|
137
|
+
value: T extends KeyValue<unknown, infer V> ? V : never
|
|
138
|
+
) => Ve,
|
|
139
|
+
options?: OrderByOptions<Ve>
|
|
140
|
+
) {
|
|
141
|
+
const limit = options?.limit ?? Infinity
|
|
142
|
+
const offset = options?.offset ?? 0
|
|
143
|
+
const comparator =
|
|
144
|
+
options?.comparator ??
|
|
145
|
+
((a, b) => {
|
|
146
|
+
// Default to JS like ordering
|
|
147
|
+
if (a === b) return 0
|
|
148
|
+
if (a < b) return -1
|
|
149
|
+
return 1
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
stream: IStreamBuilder<T>
|
|
154
|
+
): IStreamBuilder<
|
|
155
|
+
KeyValue<
|
|
156
|
+
T extends KeyValue<infer K, unknown> ? K : never,
|
|
157
|
+
[T extends KeyValue<unknown, infer V> ? V : never, string]
|
|
158
|
+
>
|
|
159
|
+
> => {
|
|
160
|
+
type KeyType = T extends KeyValue<infer K, unknown> ? K : never
|
|
161
|
+
type ValueType = T extends KeyValue<unknown, infer V> ? V : never
|
|
162
|
+
|
|
163
|
+
return stream.pipe(
|
|
164
|
+
map(
|
|
165
|
+
([key, value]) =>
|
|
166
|
+
[
|
|
167
|
+
null,
|
|
168
|
+
[
|
|
169
|
+
key,
|
|
170
|
+
valueExtractor(
|
|
171
|
+
value as T extends KeyValue<unknown, infer V> ? V : never
|
|
172
|
+
),
|
|
173
|
+
],
|
|
174
|
+
] as KeyValue<null, [KeyType, Ve]>
|
|
175
|
+
),
|
|
176
|
+
topKFunction((a, b) => comparator(a[1], b[1]), {
|
|
177
|
+
limit,
|
|
178
|
+
offset,
|
|
179
|
+
}),
|
|
180
|
+
map(([_, [[key], index]]) => [key, index] as KeyValue<KeyType, string>),
|
|
181
|
+
innerJoin(stream),
|
|
182
|
+
map(([key, [index, value]]) => {
|
|
183
|
+
return [key, [value, index]] as KeyValue<KeyType, [ValueType, string]>
|
|
184
|
+
}),
|
|
185
|
+
consolidate()
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Orders the elements and limits the number of results, with optional offset and
|
|
192
|
+
* annotates the value with a fractional index.
|
|
193
|
+
* This requires a keyed stream, and uses the `topKWithFractionalIndex` operator to order all the elements.
|
|
194
|
+
*
|
|
195
|
+
* @param valueExtractor - A function that extracts the value to order by from the element
|
|
196
|
+
* @param options - An optional object containing comparator, limit and offset properties
|
|
197
|
+
* @returns A piped operator that orders the elements and limits the number of results
|
|
198
|
+
*/
|
|
199
|
+
export function orderByWithFractionalIndex<
|
|
200
|
+
T extends KeyValue<unknown, unknown>,
|
|
201
|
+
Ve = unknown,
|
|
202
|
+
>(
|
|
203
|
+
valueExtractor: (
|
|
204
|
+
value: T extends KeyValue<unknown, infer V> ? V : never
|
|
205
|
+
) => Ve,
|
|
206
|
+
options?: OrderByOptions<Ve>
|
|
207
|
+
) {
|
|
208
|
+
return orderByWithFractionalIndexBase(
|
|
209
|
+
topKWithFractionalIndex,
|
|
210
|
+
valueExtractor,
|
|
211
|
+
options
|
|
212
|
+
)
|
|
213
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { orderByWithFractionalIndexBase } from "./orderBy.js"
|
|
2
|
+
import { topKWithFractionalIndexBTree } from "./topKWithFractionalIndexBTree.js"
|
|
3
|
+
import type { KeyValue } from "../types.js"
|
|
4
|
+
import type { OrderByOptions } from "./orderBy.js"
|
|
5
|
+
|
|
6
|
+
export function orderByWithFractionalIndexBTree<
|
|
7
|
+
T extends KeyValue<unknown, unknown>,
|
|
8
|
+
Ve = unknown,
|
|
9
|
+
>(
|
|
10
|
+
valueExtractor: (
|
|
11
|
+
value: T extends KeyValue<unknown, infer V> ? V : never
|
|
12
|
+
) => Ve,
|
|
13
|
+
options?: OrderByOptions<Ve>
|
|
14
|
+
) {
|
|
15
|
+
return orderByWithFractionalIndexBase(
|
|
16
|
+
topKWithFractionalIndexBTree,
|
|
17
|
+
valueExtractor,
|
|
18
|
+
options
|
|
19
|
+
)
|
|
20
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { DifferenceStreamWriter, UnaryOperator } from "../graph.js"
|
|
2
|
+
import { StreamBuilder } from "../d2.js"
|
|
3
|
+
import type { IStreamBuilder, PipedOperator } from "../types.js"
|
|
4
|
+
import type { DifferenceStreamReader } from "../graph.js"
|
|
5
|
+
import type { MultiSet } from "../multiset.js"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Operator that outputs the messages in the stream
|
|
9
|
+
*/
|
|
10
|
+
export class OutputOperator<T> extends UnaryOperator<T> {
|
|
11
|
+
#fn: (data: MultiSet<T>) => void
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
id: number,
|
|
15
|
+
inputA: DifferenceStreamReader<T>,
|
|
16
|
+
outputWriter: DifferenceStreamWriter<T>,
|
|
17
|
+
fn: (data: MultiSet<T>) => void
|
|
18
|
+
) {
|
|
19
|
+
super(id, inputA, outputWriter)
|
|
20
|
+
this.#fn = fn
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
run(): void {
|
|
24
|
+
for (const message of this.inputMessages()) {
|
|
25
|
+
this.#fn(message)
|
|
26
|
+
this.output.sendData(message)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Outputs the messages in the stream
|
|
33
|
+
* @param fn - The function to call with each message
|
|
34
|
+
*/
|
|
35
|
+
export function output<T>(
|
|
36
|
+
fn: (data: MultiSet<T>) => void
|
|
37
|
+
): PipedOperator<T, T> {
|
|
38
|
+
return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {
|
|
39
|
+
const outputStream = new StreamBuilder<T>(
|
|
40
|
+
stream.graph,
|
|
41
|
+
new DifferenceStreamWriter<T>()
|
|
42
|
+
)
|
|
43
|
+
const operator = new OutputOperator<T>(
|
|
44
|
+
stream.graph.getNextOperatorId(),
|
|
45
|
+
stream.connectReader(),
|
|
46
|
+
outputStream.writer,
|
|
47
|
+
fn
|
|
48
|
+
)
|
|
49
|
+
stream.graph.addOperator(operator)
|
|
50
|
+
stream.graph.addStream(outputStream.connectReader())
|
|
51
|
+
return outputStream
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { IStreamBuilder, PipedOperator } from "../types.js"
|
|
2
|
+
|
|
3
|
+
// Don't judge, this is the only way to type this function.
|
|
4
|
+
// rxjs has very similar code to type its pipe function
|
|
5
|
+
// https://github.com/ReactiveX/rxjs/blob/master/packages/rxjs/src/internal/util/pipe.ts
|
|
6
|
+
// We go to 20 operators deep, because surely that's enough for anyone...
|
|
7
|
+
// A user can always split the pipe into multiple pipes to get around this.
|
|
8
|
+
export function pipe<T, O>(o1: PipedOperator<T, O>): PipedOperator<T, O>
|
|
9
|
+
// prettier-ignore
|
|
10
|
+
export function pipe<T, T2, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, O>): PipedOperator<T, O>
|
|
11
|
+
// prettier-ignore
|
|
12
|
+
export function pipe<T, T2, T3, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, O>): PipedOperator<T, O>
|
|
13
|
+
// prettier-ignore
|
|
14
|
+
export function pipe<T, T2, T3, T4, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, O>): PipedOperator<T, O>
|
|
15
|
+
// prettier-ignore
|
|
16
|
+
export function pipe<T, T2, T3, T4, T5, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, O>): PipedOperator<T, O>
|
|
17
|
+
// prettier-ignore
|
|
18
|
+
export function pipe<T, T2, T3, T4, T5, T6, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, O>): PipedOperator<T, O>
|
|
19
|
+
// prettier-ignore
|
|
20
|
+
export function pipe<T, T2, T3, T4, T5, T6, T7, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, O>): PipedOperator<T, O>
|
|
21
|
+
// prettier-ignore
|
|
22
|
+
export function pipe<T, T2, T3, T4, T5, T6, T7, T8, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, O>): PipedOperator<T, O>
|
|
23
|
+
// prettier-ignore
|
|
24
|
+
export function pipe<T, T2, T3, T4, T5, T6, T7, T8, T9, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, O>): PipedOperator<T, O>
|
|
25
|
+
// prettier-ignore
|
|
26
|
+
export function pipe<T, T2, T3, T4, T5, T6, T7, T8, T9, T10, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, O>): PipedOperator<T, O>
|
|
27
|
+
// prettier-ignore
|
|
28
|
+
export function pipe<T, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, O>): PipedOperator<T, O>
|
|
29
|
+
// prettier-ignore
|
|
30
|
+
export function pipe<T, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, T12>, o12: PipedOperator<T12, O>): PipedOperator<T, O>
|
|
31
|
+
// prettier-ignore
|
|
32
|
+
export function pipe<T, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, T12>, o12: PipedOperator<T12, T13>, o13: PipedOperator<T13, O>): PipedOperator<T, O>
|
|
33
|
+
// prettier-ignore
|
|
34
|
+
export function pipe<T, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, T12>, o12: PipedOperator<T12, T13>, o13: PipedOperator<T13, T14>, o14: PipedOperator<T14, O>): PipedOperator<T, O>
|
|
35
|
+
// prettier-ignore
|
|
36
|
+
export function pipe<T, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, T12>, o12: PipedOperator<T12, T13>, o13: PipedOperator<T13, T14>, o14: PipedOperator<T14, T15>, o15: PipedOperator<T15, O>): PipedOperator<T, O>
|
|
37
|
+
// prettier-ignore
|
|
38
|
+
export function pipe<T, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, T12>, o12: PipedOperator<T12, T13>, o13: PipedOperator<T13, T14>, o14: PipedOperator<T14, T15>, o15: PipedOperator<T15, T16>, o16: PipedOperator<T16, O>): PipedOperator<T, O>
|
|
39
|
+
// prettier-ignore
|
|
40
|
+
export function pipe<T, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, T12>, o12: PipedOperator<T12, T13>, o13: PipedOperator<T13, T14>, o14: PipedOperator<T14, T15>, o15: PipedOperator<T15, T16>, o16: PipedOperator<T16, T17>, o17: PipedOperator<T17, O>): PipedOperator<T, O>
|
|
41
|
+
// prettier-ignore
|
|
42
|
+
export function pipe<T, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, T12>, o12: PipedOperator<T12, T13>, o13: PipedOperator<T13, T14>, o14: PipedOperator<T14, T15>, o15: PipedOperator<T15, T16>, o16: PipedOperator<T16, T17>, o17: PipedOperator<T17, T18>, o18: PipedOperator<T18, O>): PipedOperator<T, O>
|
|
43
|
+
// prettier-ignore
|
|
44
|
+
export function pipe<T, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, T12>, o12: PipedOperator<T12, T13>, o13: PipedOperator<T13, T14>, o14: PipedOperator<T14, T15>, o15: PipedOperator<T15, T16>, o16: PipedOperator<T16, T17>, o17: PipedOperator<T17, T18>, o18: PipedOperator<T18, T19>, o19: PipedOperator<T19, O>): PipedOperator<T, O>
|
|
45
|
+
// prettier-ignore
|
|
46
|
+
export function pipe<T, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, T12>, o12: PipedOperator<T12, T13>, o13: PipedOperator<T13, T14>, o14: PipedOperator<T14, T15>, o15: PipedOperator<T15, T16>, o16: PipedOperator<T16, T17>, o17: PipedOperator<T17, T18>, o18: PipedOperator<T18, T19>, o19: PipedOperator<T19, T20>, o20: PipedOperator<T20, O>): PipedOperator<T, O>
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Creates a new stream by piping the input stream through a series of operators
|
|
50
|
+
*/
|
|
51
|
+
export function pipe<T>(...operators: Array<PipedOperator<any, any>>) {
|
|
52
|
+
return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {
|
|
53
|
+
return stream.pipe(...operators)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { DifferenceStreamWriter, UnaryOperator } from "../graph.js"
|
|
2
|
+
import { StreamBuilder } from "../d2.js"
|
|
3
|
+
import { MultiSet } from "../multiset.js"
|
|
4
|
+
import { Index } from "../indexes.js"
|
|
5
|
+
import type { DifferenceStreamReader } from "../graph.js"
|
|
6
|
+
import type { IStreamBuilder, KeyValue } from "../types.js"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Base operator for reduction operations (version-free)
|
|
10
|
+
*/
|
|
11
|
+
export class ReduceOperator<K, V1, V2> extends UnaryOperator<[K, V1], [K, V2]> {
|
|
12
|
+
#index = new Index<K, V1>()
|
|
13
|
+
#indexOut = new Index<K, V2>()
|
|
14
|
+
#f: (values: Array<[V1, number]>) => Array<[V2, number]>
|
|
15
|
+
|
|
16
|
+
constructor(
|
|
17
|
+
id: number,
|
|
18
|
+
inputA: DifferenceStreamReader<[K, V1]>,
|
|
19
|
+
output: DifferenceStreamWriter<[K, V2]>,
|
|
20
|
+
f: (values: Array<[V1, number]>) => Array<[V2, number]>
|
|
21
|
+
) {
|
|
22
|
+
super(id, inputA, output)
|
|
23
|
+
this.#f = f
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
run(): void {
|
|
27
|
+
// Collect all input messages and update the index
|
|
28
|
+
const keysTodo = new Set<K>()
|
|
29
|
+
for (const message of this.inputMessages()) {
|
|
30
|
+
for (const [item, multiplicity] of message.getInner()) {
|
|
31
|
+
const [key, value] = item
|
|
32
|
+
this.#index.addValue(key, [value, multiplicity])
|
|
33
|
+
keysTodo.add(key)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// For each key, compute the reduction and delta
|
|
38
|
+
const result: Array<[[K, V2], number]> = []
|
|
39
|
+
for (const key of keysTodo) {
|
|
40
|
+
const curr = this.#index.get(key)
|
|
41
|
+
const currOut = this.#indexOut.get(key)
|
|
42
|
+
const out = this.#f(curr)
|
|
43
|
+
|
|
44
|
+
// Create maps for current and previous outputs using values directly as keys
|
|
45
|
+
const newOutputMap = new Map<V2, number>()
|
|
46
|
+
const oldOutputMap = new Map<V2, number>()
|
|
47
|
+
|
|
48
|
+
// Process new output
|
|
49
|
+
for (const [value, multiplicity] of out) {
|
|
50
|
+
const existing = newOutputMap.get(value) ?? 0
|
|
51
|
+
newOutputMap.set(value, existing + multiplicity)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Process previous output
|
|
55
|
+
for (const [value, multiplicity] of currOut) {
|
|
56
|
+
const existing = oldOutputMap.get(value) ?? 0
|
|
57
|
+
oldOutputMap.set(value, existing + multiplicity)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// First, emit removals for old values that are no longer present
|
|
61
|
+
for (const [value, multiplicity] of oldOutputMap) {
|
|
62
|
+
if (!newOutputMap.has(value)) {
|
|
63
|
+
// Remove the old value entirely
|
|
64
|
+
result.push([[key, value], -multiplicity])
|
|
65
|
+
this.#indexOut.addValue(key, [value, -multiplicity])
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Then, emit additions for new values that are not present in old
|
|
70
|
+
for (const [value, multiplicity] of newOutputMap) {
|
|
71
|
+
if (!oldOutputMap.has(value)) {
|
|
72
|
+
// Add the new value only if it has non-zero multiplicity
|
|
73
|
+
if (multiplicity !== 0) {
|
|
74
|
+
result.push([[key, value], multiplicity])
|
|
75
|
+
this.#indexOut.addValue(key, [value, multiplicity])
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Finally, emit multiplicity changes for values that were present and are still present
|
|
81
|
+
for (const [value, newMultiplicity] of newOutputMap) {
|
|
82
|
+
const oldMultiplicity = oldOutputMap.get(value)
|
|
83
|
+
if (oldMultiplicity !== undefined) {
|
|
84
|
+
const delta = newMultiplicity - oldMultiplicity
|
|
85
|
+
// Only emit actual changes, i.e. non-zero deltas
|
|
86
|
+
if (delta !== 0) {
|
|
87
|
+
result.push([[key, value], delta])
|
|
88
|
+
this.#indexOut.addValue(key, [value, delta])
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (result.length > 0) {
|
|
95
|
+
this.output.sendData(new MultiSet(result))
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Reduces the elements in the stream by key (version-free)
|
|
102
|
+
*/
|
|
103
|
+
export function reduce<
|
|
104
|
+
KType extends T extends KeyValue<infer K, infer _V> ? K : never,
|
|
105
|
+
V1Type extends T extends KeyValue<KType, infer V> ? V : never,
|
|
106
|
+
R,
|
|
107
|
+
T,
|
|
108
|
+
>(f: (values: Array<[V1Type, number]>) => Array<[R, number]>) {
|
|
109
|
+
return (stream: IStreamBuilder<T>): IStreamBuilder<KeyValue<KType, R>> => {
|
|
110
|
+
const output = new StreamBuilder<KeyValue<KType, R>>(
|
|
111
|
+
stream.graph,
|
|
112
|
+
new DifferenceStreamWriter<KeyValue<KType, R>>()
|
|
113
|
+
)
|
|
114
|
+
const operator = new ReduceOperator<KType, V1Type, R>(
|
|
115
|
+
stream.graph.getNextOperatorId(),
|
|
116
|
+
stream.connectReader() as DifferenceStreamReader<KeyValue<KType, V1Type>>,
|
|
117
|
+
output.writer,
|
|
118
|
+
f
|
|
119
|
+
)
|
|
120
|
+
stream.graph.addOperator(operator)
|
|
121
|
+
stream.graph.addStream(output.connectReader())
|
|
122
|
+
return output
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { MultiSet } from "../multiset.js"
|
|
2
|
+
import { reduce } from "./reduce.js"
|
|
3
|
+
import type { IStreamBuilder, PipedOperator } from "../types"
|
|
4
|
+
import type { KeyValue } from "../types.js"
|
|
5
|
+
|
|
6
|
+
interface TopKOptions {
|
|
7
|
+
limit?: number
|
|
8
|
+
offset?: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Limits the number of results based on a comparator, with optional offset.
|
|
13
|
+
* This works on a keyed stream, where the key is the first element of the tuple
|
|
14
|
+
* The ordering is withing a key group, i.e. elements are sorted within a key group
|
|
15
|
+
* and the limit + offset is applied to that sorted group.
|
|
16
|
+
* To order the entire stream, key by the same value for all elements such as null.
|
|
17
|
+
*
|
|
18
|
+
* @param comparator - A function that compares two elements
|
|
19
|
+
* @param options - An optional object containing limit and offset properties
|
|
20
|
+
* @returns A piped operator that limits the number of results
|
|
21
|
+
*/
|
|
22
|
+
export function topK<
|
|
23
|
+
KType extends T extends KeyValue<infer K, infer _V> ? K : never,
|
|
24
|
+
V1Type extends T extends KeyValue<KType, infer V> ? V : never,
|
|
25
|
+
T,
|
|
26
|
+
>(
|
|
27
|
+
comparator: (a: V1Type, b: V1Type) => number,
|
|
28
|
+
options?: TopKOptions
|
|
29
|
+
): PipedOperator<T, T> {
|
|
30
|
+
const limit = options?.limit ?? Infinity
|
|
31
|
+
const offset = options?.offset ?? 0
|
|
32
|
+
|
|
33
|
+
return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {
|
|
34
|
+
const reduced = stream.pipe(
|
|
35
|
+
reduce((values) => {
|
|
36
|
+
// `values` is a list of tuples, first element is the value, second is the multiplicity
|
|
37
|
+
const consolidated = new MultiSet(values).consolidate()
|
|
38
|
+
const sortedValues = consolidated
|
|
39
|
+
.getInner()
|
|
40
|
+
.sort((a, b) => comparator(a[0] as V1Type, b[0] as V1Type))
|
|
41
|
+
return sortedValues.slice(offset, offset + limit)
|
|
42
|
+
})
|
|
43
|
+
)
|
|
44
|
+
return reduced as IStreamBuilder<T>
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Limits the number of results based on a comparator, with optional offset.
|
|
50
|
+
* This works on a keyed stream, where the key is the first element of the tuple
|
|
51
|
+
* The ordering is withing a key group, i.e. elements are sorted within a key group
|
|
52
|
+
* and the limit + offset is applied to that sorted group.
|
|
53
|
+
* To order the entire stream, key by the same value for all elements such as null.
|
|
54
|
+
* Adds the index of the element to the result as [key, [value, index]]
|
|
55
|
+
*
|
|
56
|
+
* @param comparator - A function that compares two elements
|
|
57
|
+
* @param options - An optional object containing limit and offset properties
|
|
58
|
+
* @returns A piped operator that orders the elements and limits the number of results
|
|
59
|
+
*/
|
|
60
|
+
export function topKWithIndex<
|
|
61
|
+
KType extends T extends KeyValue<infer K, infer _V> ? K : never,
|
|
62
|
+
V1Type extends T extends KeyValue<KType, infer V> ? V : never,
|
|
63
|
+
T,
|
|
64
|
+
>(
|
|
65
|
+
comparator: (a: V1Type, b: V1Type) => number,
|
|
66
|
+
options?: TopKOptions
|
|
67
|
+
): PipedOperator<T, KeyValue<KType, [V1Type, number]>> {
|
|
68
|
+
const limit = options?.limit ?? Infinity
|
|
69
|
+
const offset = options?.offset ?? 0
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
stream: IStreamBuilder<T>
|
|
73
|
+
): IStreamBuilder<KeyValue<KType, [V1Type, number]>> => {
|
|
74
|
+
const reduced = stream.pipe(
|
|
75
|
+
reduce<KType, V1Type, [V1Type, number], T>((values) => {
|
|
76
|
+
// `values` is a list of tuples, first element is the value, second is the multiplicity
|
|
77
|
+
const consolidated = new MultiSet(values).consolidate()
|
|
78
|
+
let i = offset
|
|
79
|
+
const sortedValues = consolidated
|
|
80
|
+
.getInner()
|
|
81
|
+
.sort((a, b) => comparator(a[0], b[0]))
|
|
82
|
+
.slice(offset, offset + limit)
|
|
83
|
+
.map(([value, multiplicity]): [[V1Type, number], number] => [
|
|
84
|
+
[value, i++],
|
|
85
|
+
multiplicity,
|
|
86
|
+
])
|
|
87
|
+
return sortedValues
|
|
88
|
+
})
|
|
89
|
+
)
|
|
90
|
+
return reduced
|
|
91
|
+
}
|
|
92
|
+
}
|