@tanstack/db-ivm
Version:
Incremental View Maintenance for TanStack DB based on Differential Dataflow
93 lines (87 loc) • 3.54 kB
text/typescript
import { MultiSet } from "../multiset.js"
import { reduce } from "./reduce.js"
import type { IStreamBuilder, PipedOperator } from "../types"
import type { KeyValue } from "../types.js"
interface TopKOptions {
limit?: number
offset?: number
}
/**
* Limits the number of results based on a comparator, with optional offset.
* This works on a keyed stream, where the key is the first element of the tuple
* The ordering is withing a key group, i.e. elements are sorted within a key group
* and the limit + offset is applied to that sorted group.
* To order the entire stream, key by the same value for all elements such as null.
*
* @param comparator - A function that compares two elements
* @param options - An optional object containing limit and offset properties
* @returns A piped operator that limits the number of results
*/
export function topK<
KType extends T extends KeyValue<infer K, infer _V> ? K : never,
V1Type extends T extends KeyValue<KType, infer V> ? V : never,
T,
>(
comparator: (a: V1Type, b: V1Type) => number,
options?: TopKOptions
): PipedOperator<T, T> {
const limit = options?.limit ?? Infinity
const offset = options?.offset ?? 0
return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {
const reduced = stream.pipe(
reduce((values) => {
// `values` is a list of tuples, first element is the value, second is the multiplicity
const consolidated = new MultiSet(values).consolidate()
const sortedValues = consolidated
.getInner()
.sort((a, b) => comparator(a[0] as V1Type, b[0] as V1Type))
return sortedValues.slice(offset, offset + limit)
})
)
return reduced as IStreamBuilder<T>
}
}
/**
* Limits the number of results based on a comparator, with optional offset.
* This works on a keyed stream, where the key is the first element of the tuple
* The ordering is withing a key group, i.e. elements are sorted within a key group
* and the limit + offset is applied to that sorted group.
* To order the entire stream, key by the same value for all elements such as null.
* Adds the index of the element to the result as [key, [value, index]]
*
* @param comparator - A function that compares two elements
* @param options - An optional object containing limit and offset properties
* @returns A piped operator that orders the elements and limits the number of results
*/
export function topKWithIndex<
KType extends T extends KeyValue<infer K, infer _V> ? K : never,
V1Type extends T extends KeyValue<KType, infer V> ? V : never,
T,
>(
comparator: (a: V1Type, b: V1Type) => number,
options?: TopKOptions
): PipedOperator<T, KeyValue<KType, [V1Type, number]>> {
const limit = options?.limit ?? Infinity
const offset = options?.offset ?? 0
return (
stream: IStreamBuilder<T>
): IStreamBuilder<KeyValue<KType, [V1Type, number]>> => {
const reduced = stream.pipe(
reduce<KType, V1Type, [V1Type, number], T>((values) => {
// `values` is a list of tuples, first element is the value, second is the multiplicity
const consolidated = new MultiSet(values).consolidate()
let i = offset
const sortedValues = consolidated
.getInner()
.sort((a, b) => comparator(a[0], b[0]))
.slice(offset, offset + limit)
.map(([value, multiplicity]): [[V1Type, number], number] => [
[value, i++],
multiplicity,
])
return sortedValues
})
)
return reduced
}
}