@tanstack/db-ivm 0.1.26 → 0.2.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 +5 -6
- package/dist/cjs/hashing/hash.cjs +58 -3
- package/dist/cjs/hashing/hash.cjs.map +1 -1
- package/dist/cjs/hashing/hash.d.cts +9 -0
- package/dist/cjs/hashing/murmur.cjs +26 -15
- package/dist/cjs/hashing/murmur.cjs.map +1 -1
- package/dist/cjs/hashing/murmur.d.cts +2 -0
- package/dist/cjs/index.cjs +0 -26
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/indexes.cjs +52 -16
- package/dist/cjs/indexes.cjs.map +1 -1
- package/dist/cjs/indexes.d.cts +4 -1
- package/dist/cjs/operators/groupBy.cjs +33 -36
- package/dist/cjs/operators/groupBy.cjs.map +1 -1
- package/dist/cjs/operators/groupBy.d.cts +1 -5
- package/dist/cjs/operators/groupedTopKWithFractionalIndex.cjs +3 -9
- package/dist/cjs/operators/groupedTopKWithFractionalIndex.cjs.map +1 -1
- package/dist/cjs/operators/groupedTopKWithFractionalIndex.d.cts +1 -15
- package/dist/cjs/operators/index.d.cts +0 -6
- package/dist/cjs/operators/join.cjs +0 -20
- package/dist/cjs/operators/join.cjs.map +1 -1
- package/dist/cjs/operators/join.d.cts +0 -40
- package/dist/cjs/operators/orderBy.cjs +2 -77
- package/dist/cjs/operators/orderBy.cjs.map +1 -1
- package/dist/cjs/operators/orderBy.d.cts +2 -31
- package/dist/cjs/operators/reduce.cjs +36 -5
- package/dist/cjs/operators/reduce.cjs.map +1 -1
- package/dist/cjs/operators/reduce.d.cts +11 -2
- package/dist/cjs/operators/topKArray.cjs +9 -0
- package/dist/cjs/operators/topKArray.cjs.map +1 -1
- package/dist/cjs/operators/topKArray.d.cts +6 -0
- package/dist/cjs/operators/topKState.cjs +3 -0
- package/dist/cjs/operators/topKState.cjs.map +1 -1
- package/dist/cjs/operators/topKState.d.cts +1 -0
- package/dist/cjs/operators/topKWithFractionalIndex.cjs +5 -9
- package/dist/cjs/operators/topKWithFractionalIndex.cjs.map +1 -1
- package/dist/cjs/operators/topKWithFractionalIndex.d.cts +7 -12
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +0 -7
- package/dist/esm/hashing/hash.d.ts +9 -0
- package/dist/esm/hashing/hash.js +60 -5
- package/dist/esm/hashing/hash.js.map +1 -1
- package/dist/esm/hashing/murmur.d.ts +2 -0
- package/dist/esm/hashing/murmur.js +26 -15
- package/dist/esm/hashing/murmur.js.map +1 -1
- package/dist/esm/index.js +3 -29
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/indexes.d.ts +4 -1
- package/dist/esm/indexes.js +52 -16
- package/dist/esm/indexes.js.map +1 -1
- package/dist/esm/operators/groupBy.d.ts +1 -5
- package/dist/esm/operators/groupBy.js +33 -36
- package/dist/esm/operators/groupBy.js.map +1 -1
- package/dist/esm/operators/groupedTopKWithFractionalIndex.d.ts +1 -6
- package/dist/esm/operators/groupedTopKWithFractionalIndex.js +3 -9
- package/dist/esm/operators/groupedTopKWithFractionalIndex.js.map +1 -1
- package/dist/esm/operators/index.d.ts +0 -6
- package/dist/esm/operators/join.d.ts +0 -25
- package/dist/esm/operators/join.js +1 -21
- package/dist/esm/operators/join.js.map +1 -1
- package/dist/esm/operators/orderBy.d.ts +2 -22
- package/dist/esm/operators/orderBy.js +3 -78
- package/dist/esm/operators/orderBy.js.map +1 -1
- package/dist/esm/operators/reduce.d.ts +11 -2
- package/dist/esm/operators/reduce.js +36 -5
- package/dist/esm/operators/reduce.js.map +1 -1
- package/dist/esm/operators/topKArray.d.ts +6 -0
- package/dist/esm/operators/topKArray.js +9 -0
- package/dist/esm/operators/topKArray.js.map +1 -1
- package/dist/esm/operators/topKState.d.ts +1 -0
- package/dist/esm/operators/topKState.js +3 -0
- package/dist/esm/operators/topKState.js.map +1 -1
- package/dist/esm/operators/topKWithFractionalIndex.d.ts +7 -3
- package/dist/esm/operators/topKWithFractionalIndex.js +6 -10
- package/dist/esm/operators/topKWithFractionalIndex.js.map +1 -1
- package/dist/esm/utils.d.ts +0 -7
- package/dist/esm/utils.js.map +1 -1
- package/package.json +2 -4
- package/src/hashing/hash.ts +87 -5
- package/src/hashing/murmur.ts +31 -17
- package/src/indexes.ts +65 -16
- package/src/operators/groupBy.ts +48 -58
- package/src/operators/groupedTopKWithFractionalIndex.ts +4 -15
- package/src/operators/index.ts +0 -6
- package/src/operators/join.ts +0 -90
- package/src/operators/orderBy.ts +6 -141
- package/src/operators/reduce.ts +60 -3
- package/src/operators/topKArray.ts +15 -0
- package/src/operators/topKState.ts +7 -0
- package/src/operators/topKWithFractionalIndex.ts +15 -16
- package/src/utils.ts +0 -30
- package/dist/cjs/operators/count.cjs +0 -35
- package/dist/cjs/operators/count.cjs.map +0 -1
- package/dist/cjs/operators/count.d.cts +0 -19
- package/dist/cjs/operators/debug.cjs +0 -39
- package/dist/cjs/operators/debug.cjs.map +0 -1
- package/dist/cjs/operators/debug.d.cts +0 -16
- package/dist/cjs/operators/filterBy.cjs +0 -19
- package/dist/cjs/operators/filterBy.cjs.map +0 -1
- package/dist/cjs/operators/filterBy.d.cts +0 -9
- package/dist/cjs/operators/keying.cjs +0 -16
- package/dist/cjs/operators/keying.cjs.map +0 -1
- package/dist/cjs/operators/keying.d.cts +0 -22
- package/dist/cjs/operators/negate.cjs +0 -27
- package/dist/cjs/operators/negate.cjs.map +0 -1
- package/dist/cjs/operators/negate.d.cts +0 -13
- package/dist/cjs/operators/topK.cjs +0 -39
- package/dist/cjs/operators/topK.cjs.map +0 -1
- package/dist/cjs/operators/topK.d.cts +0 -34
- package/dist/cjs/operators/topKWithFractionalIndexBTree.d.cts +0 -43
- package/dist/esm/operators/count.d.ts +0 -13
- package/dist/esm/operators/count.js +0 -35
- package/dist/esm/operators/count.js.map +0 -1
- package/dist/esm/operators/debug.d.ts +0 -16
- package/dist/esm/operators/debug.js +0 -39
- package/dist/esm/operators/debug.js.map +0 -1
- package/dist/esm/operators/filterBy.d.ts +0 -9
- package/dist/esm/operators/filterBy.js +0 -19
- package/dist/esm/operators/filterBy.js.map +0 -1
- package/dist/esm/operators/keying.d.ts +0 -19
- package/dist/esm/operators/keying.js +0 -16
- package/dist/esm/operators/keying.js.map +0 -1
- package/dist/esm/operators/negate.d.ts +0 -13
- package/dist/esm/operators/negate.js +0 -27
- package/dist/esm/operators/negate.js.map +0 -1
- package/dist/esm/operators/topK.d.ts +0 -31
- package/dist/esm/operators/topK.js +0 -39
- package/dist/esm/operators/topK.js.map +0 -1
- package/dist/esm/operators/topKWithFractionalIndexBTree.d.ts +0 -28
- package/src/operators/count.ts +0 -51
- package/src/operators/debug.ts +0 -57
- package/src/operators/filterBy.ts +0 -30
- package/src/operators/keying.ts +0 -34
- package/src/operators/negate.ts +0 -32
- package/src/operators/topK.ts +0 -91
- package/src/operators/topKWithFractionalIndexBTree.ts +0 -297
package/src/operators/count.ts
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { DifferenceStreamWriter } from '../graph.js'
|
|
2
|
-
import { StreamBuilder } from '../d2.js'
|
|
3
|
-
import { ReduceOperator } from './reduce.js'
|
|
4
|
-
import type { DifferenceStreamReader } from '../graph.js'
|
|
5
|
-
import type { IStreamBuilder, KeyValue } from '../types.js'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Operator that counts elements by key (version-free)
|
|
9
|
-
*/
|
|
10
|
-
export class CountOperator<K, V> extends ReduceOperator<K, V, number> {
|
|
11
|
-
constructor(
|
|
12
|
-
id: number,
|
|
13
|
-
inputA: DifferenceStreamReader<[K, V]>,
|
|
14
|
-
output: DifferenceStreamWriter<[K, number]>,
|
|
15
|
-
) {
|
|
16
|
-
const countInner = (vals: Array<[V, number]>): Array<[number, number]> => {
|
|
17
|
-
let totalCount = 0
|
|
18
|
-
for (const [_, diff] of vals) {
|
|
19
|
-
totalCount += diff
|
|
20
|
-
}
|
|
21
|
-
return [[totalCount, 1]]
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
super(id, inputA, output, countInner)
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Counts the number of elements by key (version-free)
|
|
30
|
-
*/
|
|
31
|
-
export function count<
|
|
32
|
-
KType extends (T extends KeyValue<infer K, infer _V> ? K : never),
|
|
33
|
-
VType extends (T extends KeyValue<KType, infer V> ? V : never),
|
|
34
|
-
T,
|
|
35
|
-
>() {
|
|
36
|
-
return (
|
|
37
|
-
stream: IStreamBuilder<T>,
|
|
38
|
-
): IStreamBuilder<KeyValue<KType, number>> => {
|
|
39
|
-
const output = new StreamBuilder<KeyValue<KType, number>>(
|
|
40
|
-
stream.graph,
|
|
41
|
-
new DifferenceStreamWriter<KeyValue<KType, number>>(),
|
|
42
|
-
)
|
|
43
|
-
const operator = new CountOperator<KType, VType>(
|
|
44
|
-
stream.graph.getNextOperatorId(),
|
|
45
|
-
stream.connectReader() as DifferenceStreamReader<KeyValue<KType, VType>>,
|
|
46
|
-
output.writer,
|
|
47
|
-
)
|
|
48
|
-
stream.graph.addOperator(operator)
|
|
49
|
-
return output
|
|
50
|
-
}
|
|
51
|
-
}
|
package/src/operators/debug.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
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
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Operator that logs debug information about the stream
|
|
8
|
-
*/
|
|
9
|
-
export class DebugOperator<T> extends UnaryOperator<T> {
|
|
10
|
-
#name: string
|
|
11
|
-
#indent: boolean
|
|
12
|
-
|
|
13
|
-
constructor(
|
|
14
|
-
id: number,
|
|
15
|
-
inputA: DifferenceStreamReader<T>,
|
|
16
|
-
output: DifferenceStreamWriter<T>,
|
|
17
|
-
name: string,
|
|
18
|
-
indent: boolean = false,
|
|
19
|
-
) {
|
|
20
|
-
super(id, inputA, output)
|
|
21
|
-
this.#name = name
|
|
22
|
-
this.#indent = indent
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
run(): void {
|
|
26
|
-
for (const message of this.inputMessages()) {
|
|
27
|
-
console.log(`debug ${this.#name} data: ${message.toString(this.#indent)}`)
|
|
28
|
-
this.output.sendData(message)
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Logs debug information about the stream using console.log
|
|
35
|
-
* @param name - The name to prefix debug messages with
|
|
36
|
-
* @param indent - Whether to indent the debug output
|
|
37
|
-
*/
|
|
38
|
-
export function debug<T>(
|
|
39
|
-
name: string,
|
|
40
|
-
indent: boolean = false,
|
|
41
|
-
): PipedOperator<T, T> {
|
|
42
|
-
return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {
|
|
43
|
-
const output = new StreamBuilder<T>(
|
|
44
|
-
stream.graph,
|
|
45
|
-
new DifferenceStreamWriter<T>(),
|
|
46
|
-
)
|
|
47
|
-
const operator = new DebugOperator<T>(
|
|
48
|
-
stream.graph.getNextOperatorId(),
|
|
49
|
-
stream.connectReader(),
|
|
50
|
-
output.writer,
|
|
51
|
-
name,
|
|
52
|
-
indent,
|
|
53
|
-
)
|
|
54
|
-
stream.graph.addOperator(operator)
|
|
55
|
-
return output
|
|
56
|
-
}
|
|
57
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { map } from './map.js'
|
|
2
|
-
import { innerJoin } from './join.js'
|
|
3
|
-
import { consolidate } from './consolidate.js'
|
|
4
|
-
import type { IStreamBuilder, KeyValue, PipedOperator } from '../types.js'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Filters the elements of a keyed stream, by keys of another stream.
|
|
8
|
-
* This allows you to build pipelies where you have multiple outputs that are related,
|
|
9
|
-
* such as a streams of issues and comments for a project.
|
|
10
|
-
*
|
|
11
|
-
* @param other - The other stream to filter by, which must have the same key type as the input stream
|
|
12
|
-
*/
|
|
13
|
-
export function filterBy<
|
|
14
|
-
K,
|
|
15
|
-
V1 extends (T extends KeyValue<infer _KT, infer VT> ? VT : never),
|
|
16
|
-
T,
|
|
17
|
-
>(
|
|
18
|
-
other: IStreamBuilder<KeyValue<K, unknown>>,
|
|
19
|
-
): PipedOperator<T, KeyValue<K, V1>> {
|
|
20
|
-
return (stream: IStreamBuilder<T>): IStreamBuilder<KeyValue<K, V1>> => {
|
|
21
|
-
const otherKeys = other.pipe(
|
|
22
|
-
map(([key, _]) => [key, null] as KeyValue<K, null>),
|
|
23
|
-
)
|
|
24
|
-
return stream.pipe(
|
|
25
|
-
innerJoin(otherKeys),
|
|
26
|
-
map(([key, [value, _]]) => [key, value] as KeyValue<K, V1>),
|
|
27
|
-
consolidate(),
|
|
28
|
-
)
|
|
29
|
-
}
|
|
30
|
-
}
|
package/src/operators/keying.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { map } from './map.js'
|
|
2
|
-
import type { PipedOperator } from '../types.js'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Type for a keyed value
|
|
6
|
-
*/
|
|
7
|
-
export type Keyed<K, V> = [K, V]
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Takes an unkeyed input and returns a keyed result
|
|
11
|
-
* @param keyFn - Function to generate the key for each value
|
|
12
|
-
*/
|
|
13
|
-
export function keyBy<T, K>(
|
|
14
|
-
keyFn: (value: T) => K,
|
|
15
|
-
): PipedOperator<T, Keyed<K, T>> {
|
|
16
|
-
return map((value: T): Keyed<K, T> => [keyFn(value), value])
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Removes the key from a keyed stream
|
|
21
|
-
*/
|
|
22
|
-
export function unkey<K, V>(): PipedOperator<Keyed<K, V>, V> {
|
|
23
|
-
return map(([_, value]: Keyed<K, V>): V => value)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Takes a keyed input and rekeys it with a new key
|
|
28
|
-
* @param keyFn - Function to generate the new key for each value
|
|
29
|
-
*/
|
|
30
|
-
export function rekey<K1, K2, V>(
|
|
31
|
-
keyFn: (value: V) => K2,
|
|
32
|
-
): PipedOperator<Keyed<K1, V>, Keyed<K2, V>> {
|
|
33
|
-
return map(([_, value]: Keyed<K1, V>): Keyed<K2, V> => [keyFn(value), value])
|
|
34
|
-
}
|
package/src/operators/negate.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
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
|
-
return output
|
|
31
|
-
}
|
|
32
|
-
}
|
package/src/operators/topK.ts
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { MultiSet } from '../multiset.js'
|
|
2
|
-
import { reduce } from './reduce.js'
|
|
3
|
-
import type { IStreamBuilder, KeyValue, PipedOperator } from '../types'
|
|
4
|
-
|
|
5
|
-
interface TopKOptions {
|
|
6
|
-
limit?: number
|
|
7
|
-
offset?: number
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Limits the number of results based on a comparator, with optional offset.
|
|
12
|
-
* This works on a keyed stream, where the key is the first element of the tuple
|
|
13
|
-
* The ordering is withing a key group, i.e. elements are sorted within a key group
|
|
14
|
-
* and the limit + offset is applied to that sorted group.
|
|
15
|
-
* To order the entire stream, key by the same value for all elements such as null.
|
|
16
|
-
*
|
|
17
|
-
* @param comparator - A function that compares two elements
|
|
18
|
-
* @param options - An optional object containing limit and offset properties
|
|
19
|
-
* @returns A piped operator that limits the number of results
|
|
20
|
-
*/
|
|
21
|
-
export function topK<
|
|
22
|
-
KType extends (T extends KeyValue<infer K, infer _V> ? K : never),
|
|
23
|
-
V1Type extends (T extends KeyValue<KType, infer V> ? V : never),
|
|
24
|
-
T,
|
|
25
|
-
>(
|
|
26
|
-
comparator: (a: V1Type, b: V1Type) => number,
|
|
27
|
-
options?: TopKOptions,
|
|
28
|
-
): PipedOperator<T, T> {
|
|
29
|
-
const limit = options?.limit ?? Infinity
|
|
30
|
-
const offset = options?.offset ?? 0
|
|
31
|
-
|
|
32
|
-
return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {
|
|
33
|
-
const reduced = stream.pipe(
|
|
34
|
-
reduce((values) => {
|
|
35
|
-
// `values` is a list of tuples, first element is the value, second is the multiplicity
|
|
36
|
-
const consolidated = new MultiSet(values).consolidate()
|
|
37
|
-
const sortedValues = consolidated
|
|
38
|
-
.getInner()
|
|
39
|
-
.sort((a, b) => comparator(a[0] as V1Type, b[0] as V1Type))
|
|
40
|
-
return sortedValues.slice(offset, offset + limit)
|
|
41
|
-
}),
|
|
42
|
-
)
|
|
43
|
-
return reduced as IStreamBuilder<T>
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Limits the number of results based on a comparator, with optional offset.
|
|
49
|
-
* This works on a keyed stream, where the key is the first element of the tuple
|
|
50
|
-
* The ordering is withing a key group, i.e. elements are sorted within a key group
|
|
51
|
-
* and the limit + offset is applied to that sorted group.
|
|
52
|
-
* To order the entire stream, key by the same value for all elements such as null.
|
|
53
|
-
* Adds the index of the element to the result as [key, [value, index]]
|
|
54
|
-
*
|
|
55
|
-
* @param comparator - A function that compares two elements
|
|
56
|
-
* @param options - An optional object containing limit and offset properties
|
|
57
|
-
* @returns A piped operator that orders the elements and limits the number of results
|
|
58
|
-
*/
|
|
59
|
-
export function topKWithIndex<
|
|
60
|
-
KType extends (T extends KeyValue<infer K, infer _V> ? K : never),
|
|
61
|
-
V1Type extends (T extends KeyValue<KType, infer V> ? V : never),
|
|
62
|
-
T,
|
|
63
|
-
>(
|
|
64
|
-
comparator: (a: V1Type, b: V1Type) => number,
|
|
65
|
-
options?: TopKOptions,
|
|
66
|
-
): PipedOperator<T, KeyValue<KType, [V1Type, number]>> {
|
|
67
|
-
const limit = options?.limit ?? Infinity
|
|
68
|
-
const offset = options?.offset ?? 0
|
|
69
|
-
|
|
70
|
-
return (
|
|
71
|
-
stream: IStreamBuilder<T>,
|
|
72
|
-
): IStreamBuilder<KeyValue<KType, [V1Type, number]>> => {
|
|
73
|
-
const reduced = stream.pipe(
|
|
74
|
-
reduce<KType, V1Type, [V1Type, number], T>((values) => {
|
|
75
|
-
// `values` is a list of tuples, first element is the value, second is the multiplicity
|
|
76
|
-
const consolidated = new MultiSet(values).consolidate()
|
|
77
|
-
let i = offset
|
|
78
|
-
const sortedValues = consolidated
|
|
79
|
-
.getInner()
|
|
80
|
-
.sort((a, b) => comparator(a[0], b[0]))
|
|
81
|
-
.slice(offset, offset + limit)
|
|
82
|
-
.map(([value, multiplicity]): [[V1Type, number], number] => [
|
|
83
|
-
[value, i++],
|
|
84
|
-
multiplicity,
|
|
85
|
-
])
|
|
86
|
-
return sortedValues
|
|
87
|
-
}),
|
|
88
|
-
)
|
|
89
|
-
return reduced
|
|
90
|
-
}
|
|
91
|
-
}
|
|
@@ -1,297 +0,0 @@
|
|
|
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'
|
|
9
|
-
|
|
10
|
-
interface BTree<Key, Value> {
|
|
11
|
-
nextLowerPair: (key: Key) => [Key, Value] | undefined
|
|
12
|
-
nextHigherPair: (key: Key) => [Key, Value] | undefined
|
|
13
|
-
set: (key: Key, value: Value, overwrite?: boolean) => boolean
|
|
14
|
-
maxKey: () => Key | undefined
|
|
15
|
-
get: (key: Key, defaultValue?: Value) => Value | undefined
|
|
16
|
-
delete: (key: Key) => boolean
|
|
17
|
-
size: number
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface BTreeClass {
|
|
21
|
-
new <Key, Value>(
|
|
22
|
-
entries?: Array<[Key, Value]>,
|
|
23
|
-
compare?: (a: Key, b: Key) => number,
|
|
24
|
-
maxNodeSize?: number,
|
|
25
|
-
): BTree<Key, Value>
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
let BTree: BTreeClass | undefined
|
|
29
|
-
|
|
30
|
-
export async function loadBTree() {
|
|
31
|
-
if (BTree === undefined) {
|
|
32
|
-
const { default: _BTreeClass } = await import(`sorted-btree`)
|
|
33
|
-
BTree = _BTreeClass
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Implementation of a topK data structure that uses a B+ tree.
|
|
39
|
-
* The tree allows for logarithmic time insertions and deletions.
|
|
40
|
-
*/
|
|
41
|
-
class TopKTree<V> implements TopK<V> {
|
|
42
|
-
#comparator: (a: V, b: V) => number
|
|
43
|
-
// topK is a window at position [topKStart, topKEnd[
|
|
44
|
-
// i.e. `topKStart` is inclusive and `topKEnd` is exclusive
|
|
45
|
-
#topKStart: number
|
|
46
|
-
#topKEnd: number
|
|
47
|
-
|
|
48
|
-
#tree: BTree<V, IndexedValue<V>>
|
|
49
|
-
#topKFirstElem: IndexedValue<V> | null = null // inclusive
|
|
50
|
-
#topKLastElem: IndexedValue<V> | null = null // inclusive
|
|
51
|
-
|
|
52
|
-
constructor(
|
|
53
|
-
offset: number,
|
|
54
|
-
limit: number,
|
|
55
|
-
comparator: (a: V, b: V) => number,
|
|
56
|
-
) {
|
|
57
|
-
if (BTree === undefined) {
|
|
58
|
-
throw new Error(
|
|
59
|
-
`B+ tree not loaded. You need to call loadBTree() before using TopKTree.`,
|
|
60
|
-
)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
this.#topKStart = offset
|
|
64
|
-
this.#topKEnd = offset + limit
|
|
65
|
-
this.#comparator = comparator
|
|
66
|
-
this.#tree = new BTree(undefined, comparator)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
get size(): number {
|
|
70
|
-
const offset = this.#topKStart
|
|
71
|
-
const limit = this.#topKEnd - this.#topKStart
|
|
72
|
-
const available = this.#tree.size - offset
|
|
73
|
-
return Math.max(0, Math.min(limit, available))
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Insert a *new* value.
|
|
78
|
-
* Ignores the value if it is already present.
|
|
79
|
-
*/
|
|
80
|
-
insert(value: V): TopKChanges<V> {
|
|
81
|
-
const result: TopKChanges<V> = { moveIn: null, moveOut: null }
|
|
82
|
-
|
|
83
|
-
// Get the elements before and after the value
|
|
84
|
-
const [, indexedValueBefore] = this.#tree.nextLowerPair(value) ?? [
|
|
85
|
-
null,
|
|
86
|
-
null,
|
|
87
|
-
]
|
|
88
|
-
const [, indexedValueAfter] = this.#tree.nextHigherPair(value) ?? [
|
|
89
|
-
null,
|
|
90
|
-
null,
|
|
91
|
-
]
|
|
92
|
-
|
|
93
|
-
const indexBefore = indexedValueBefore ? getIndex(indexedValueBefore) : null
|
|
94
|
-
const indexAfter = indexedValueAfter ? getIndex(indexedValueAfter) : null
|
|
95
|
-
|
|
96
|
-
// Generate a fractional index for the value
|
|
97
|
-
// based on the fractional indices of the elements before and after it
|
|
98
|
-
const fractionalIndex = generateKeyBetween(indexBefore, indexAfter)
|
|
99
|
-
const insertedElem = indexedValue(value, fractionalIndex)
|
|
100
|
-
|
|
101
|
-
// Insert the value into the tree
|
|
102
|
-
const inserted = this.#tree.set(value, insertedElem, false)
|
|
103
|
-
if (!inserted) {
|
|
104
|
-
// The value was already present in the tree
|
|
105
|
-
// ignore this insertions since we don't support overwrites!
|
|
106
|
-
return result
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (
|
|
110
|
-
this.#topKStart === this.#topKEnd ||
|
|
111
|
-
this.#tree.size - 1 < this.#topKStart
|
|
112
|
-
) {
|
|
113
|
-
// The window is empty, or there aren't enough rows to reach its offset.
|
|
114
|
-
return result
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (this.#topKFirstElem) {
|
|
118
|
-
// We have a topK containing at least 1 element
|
|
119
|
-
if (this.#comparator(value, getValue(this.#topKFirstElem)) < 0) {
|
|
120
|
-
// The element was inserted before the topK
|
|
121
|
-
// so it moves the element that is right before the topK into the topK
|
|
122
|
-
const firstElem = getValue(this.#topKFirstElem)
|
|
123
|
-
const [, newFirstElem] = this.#tree.nextLowerPair(firstElem)!
|
|
124
|
-
this.#topKFirstElem = newFirstElem
|
|
125
|
-
result.moveIn = this.#topKFirstElem
|
|
126
|
-
} else if (
|
|
127
|
-
!this.#topKLastElem ||
|
|
128
|
-
this.#comparator(value, getValue(this.#topKLastElem)) < 0
|
|
129
|
-
) {
|
|
130
|
-
// The element was inserted within the topK
|
|
131
|
-
result.moveIn = insertedElem
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (
|
|
135
|
-
this.#topKLastElem &&
|
|
136
|
-
this.#comparator(value, getValue(this.#topKLastElem)) < 0
|
|
137
|
-
) {
|
|
138
|
-
// The element was inserted before or within the topK
|
|
139
|
-
// the newly inserted element pushes the last element of the topK out of the topK
|
|
140
|
-
// so the one before that becomes the new last element of the topK
|
|
141
|
-
const lastElem = this.#topKLastElem
|
|
142
|
-
const lastValue = getValue(lastElem)
|
|
143
|
-
const [, newLastElem] = this.#tree.nextLowerPair(lastValue)!
|
|
144
|
-
this.#topKLastElem = newLastElem
|
|
145
|
-
result.moveOut = lastElem
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// If the tree has as many elements as the offset (i.e. #topKStart)
|
|
150
|
-
// then the insertion shifted the elements 1 position to the right
|
|
151
|
-
// and the last element in the tree is now the first element of the topK
|
|
152
|
-
if (this.#tree.size - 1 === this.#topKStart) {
|
|
153
|
-
const topKFirstKey = this.#tree.maxKey()!
|
|
154
|
-
this.#topKFirstElem = this.#tree.get(topKFirstKey)!
|
|
155
|
-
result.moveIn = this.#topKFirstElem
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// By inserting this new element we now have a complete topK
|
|
159
|
-
// store the last element of the topK
|
|
160
|
-
if (this.#tree.size === this.#topKEnd) {
|
|
161
|
-
const topKLastKey = this.#tree.maxKey()!
|
|
162
|
-
this.#topKLastElem = this.#tree.get(topKLastKey)!
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return result
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
delete(value: V): TopKChanges<V> {
|
|
169
|
-
const result: TopKChanges<V> = { moveIn: null, moveOut: null }
|
|
170
|
-
|
|
171
|
-
const deletedElem = this.#tree.get(value)
|
|
172
|
-
const deleted = this.#tree.delete(value)
|
|
173
|
-
if (!deleted) {
|
|
174
|
-
return result
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (!this.#topKFirstElem) {
|
|
178
|
-
// We didn't have a topK before the delete
|
|
179
|
-
// so we still can't have a topK after the delete
|
|
180
|
-
return result
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (this.#comparator(value, getValue(this.#topKFirstElem)) <= 0) {
|
|
184
|
-
// Deleting at or before the first selected value advances that boundary.
|
|
185
|
-
result.moveOut = this.#topKFirstElem
|
|
186
|
-
// the element that was right after the first element of the topK
|
|
187
|
-
// is now the new first element of the topK
|
|
188
|
-
const firstElem = getValue(this.#topKFirstElem)
|
|
189
|
-
const [, newFirstElem] = this.#tree.nextHigherPair(firstElem) ?? [
|
|
190
|
-
null,
|
|
191
|
-
null,
|
|
192
|
-
]
|
|
193
|
-
this.#topKFirstElem = newFirstElem
|
|
194
|
-
} else if (
|
|
195
|
-
!this.#topKLastElem ||
|
|
196
|
-
// TODO: if on equal order the element is inserted *after* the already existing one
|
|
197
|
-
// then this check should become < 0
|
|
198
|
-
this.#comparator(value, getValue(this.#topKLastElem)) <= 0
|
|
199
|
-
) {
|
|
200
|
-
// The element we deleted was within the topK
|
|
201
|
-
// so we need to signal that that element is no longer in the topK
|
|
202
|
-
result.moveOut = deletedElem!
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
if (
|
|
206
|
-
this.#topKLastElem &&
|
|
207
|
-
// TODO: if on equal order the element is inserted *after* the already existing one
|
|
208
|
-
// then this check should become < 0
|
|
209
|
-
this.#comparator(value, getValue(this.#topKLastElem)) <= 0
|
|
210
|
-
) {
|
|
211
|
-
// The element we deleted was before or within the topK
|
|
212
|
-
// So the first element after the topK moved one position to the left
|
|
213
|
-
// and thus falls into the topK now
|
|
214
|
-
const lastElem = this.#topKLastElem
|
|
215
|
-
const lastValue = getValue(lastElem)
|
|
216
|
-
const [, newLastElem] = this.#tree.nextHigherPair(lastValue) ?? [
|
|
217
|
-
null,
|
|
218
|
-
null,
|
|
219
|
-
]
|
|
220
|
-
this.#topKLastElem = newLastElem
|
|
221
|
-
if (newLastElem) {
|
|
222
|
-
result.moveIn = newLastElem
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
return result
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Operator for fractional indexed topK operations
|
|
232
|
-
* This operator maintains fractional indices for sorted elements
|
|
233
|
-
* and only updates indices when elements move position
|
|
234
|
-
*/
|
|
235
|
-
export class TopKWithFractionalIndexBTreeOperator<
|
|
236
|
-
K extends string | number,
|
|
237
|
-
T,
|
|
238
|
-
> extends TopKWithFractionalIndexOperator<K, T> {
|
|
239
|
-
protected override createTopK(
|
|
240
|
-
offset: number,
|
|
241
|
-
limit: number,
|
|
242
|
-
comparator: (a: [K, T], b: [K, T]) => number,
|
|
243
|
-
): TopK<[K, T]> {
|
|
244
|
-
if (BTree === undefined) {
|
|
245
|
-
throw new Error(
|
|
246
|
-
`B+ tree not loaded. You need to call loadBTree() before using TopKWithFractionalIndexBTreeOperator.`,
|
|
247
|
-
)
|
|
248
|
-
}
|
|
249
|
-
return new TopKTree(offset, limit, comparator)
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Limits the number of results based on a comparator, with optional offset.
|
|
255
|
-
* This works on a keyed stream, where the key is the first element of the tuple.
|
|
256
|
-
* The ordering is within a key group, i.e. elements are sorted within a key group
|
|
257
|
-
* and the limit + offset is applied to that sorted group.
|
|
258
|
-
* To order the entire stream, key by the same value for all elements such as null.
|
|
259
|
-
*
|
|
260
|
-
* Uses fractional indexing to minimize the number of changes when elements move positions.
|
|
261
|
-
* Each element is assigned a fractional index that is lexicographically sortable.
|
|
262
|
-
* When elements move, only the indices of the moved elements are updated, not all elements.
|
|
263
|
-
*
|
|
264
|
-
* @param comparator - A function that compares two elements
|
|
265
|
-
* @param options - An optional object containing limit and offset properties
|
|
266
|
-
* @returns A piped operator that orders the elements and limits the number of results
|
|
267
|
-
*/
|
|
268
|
-
export function topKWithFractionalIndexBTree<KType extends string | number, T>(
|
|
269
|
-
comparator: (a: T, b: T) => number,
|
|
270
|
-
options?: TopKWithFractionalIndexOptions,
|
|
271
|
-
): PipedOperator<[KType, T], [KType, IndexedValue<T>]> {
|
|
272
|
-
const opts = options || {}
|
|
273
|
-
|
|
274
|
-
if (BTree === undefined) {
|
|
275
|
-
throw new Error(
|
|
276
|
-
`B+ tree not loaded. You need to call loadBTree() before using topKWithFractionalIndexBTree.`,
|
|
277
|
-
)
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
return (
|
|
281
|
-
stream: IStreamBuilder<[KType, T]>,
|
|
282
|
-
): IStreamBuilder<[KType, IndexedValue<T>]> => {
|
|
283
|
-
const output = new StreamBuilder<[KType, IndexedValue<T>]>(
|
|
284
|
-
stream.graph,
|
|
285
|
-
new DifferenceStreamWriter<[KType, IndexedValue<T>]>(),
|
|
286
|
-
)
|
|
287
|
-
const operator = new TopKWithFractionalIndexBTreeOperator<KType, T>(
|
|
288
|
-
stream.graph.getNextOperatorId(),
|
|
289
|
-
stream.connectReader(),
|
|
290
|
-
output.writer,
|
|
291
|
-
comparator,
|
|
292
|
-
opts,
|
|
293
|
-
)
|
|
294
|
-
stream.graph.addOperator(operator)
|
|
295
|
-
return output
|
|
296
|
-
}
|
|
297
|
-
}
|