@tanstack/db-ivm 0.1.1 → 0.1.2
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/dist/cjs/index.cjs +3 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/operators/index.d.cts +1 -0
- package/dist/cjs/operators/orderBy.cjs +3 -1
- package/dist/cjs/operators/orderBy.cjs.map +1 -1
- package/dist/cjs/operators/orderBy.d.cts +6 -2
- package/dist/cjs/operators/tap.cjs +46 -0
- package/dist/cjs/operators/tap.cjs.map +1 -0
- package/dist/cjs/operators/tap.d.cts +18 -0
- package/dist/cjs/operators/topKWithFractionalIndex.cjs +13 -3
- package/dist/cjs/operators/topKWithFractionalIndex.cjs.map +1 -1
- package/dist/cjs/operators/topKWithFractionalIndex.d.cts +2 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/operators/index.d.ts +1 -0
- package/dist/esm/operators/orderBy.d.ts +6 -2
- package/dist/esm/operators/orderBy.js +3 -1
- package/dist/esm/operators/orderBy.js.map +1 -1
- package/dist/esm/operators/tap.d.ts +18 -0
- package/dist/esm/operators/tap.js +46 -0
- package/dist/esm/operators/tap.js.map +1 -0
- package/dist/esm/operators/topKWithFractionalIndex.d.ts +2 -0
- package/dist/esm/operators/topKWithFractionalIndex.js +13 -3
- package/dist/esm/operators/topKWithFractionalIndex.js.map +1 -1
- package/package.json +1 -1
- package/src/operators/index.ts +1 -0
- package/src/operators/orderBy.ts +8 -2
- package/src/operators/tap.ts +53 -0
- package/src/operators/topKWithFractionalIndex.ts +14 -2
- package/src/operators/topKWithFractionalIndexBTree.ts +7 -0
package/dist/cjs/index.cjs
CHANGED
|
@@ -5,6 +5,7 @@ const multiset = require("./multiset.cjs");
|
|
|
5
5
|
const groupBy = require("./operators/groupBy.cjs");
|
|
6
6
|
const pipe = require("./operators/pipe.cjs");
|
|
7
7
|
const map = require("./operators/map.cjs");
|
|
8
|
+
const tap = require("./operators/tap.cjs");
|
|
8
9
|
const filter = require("./operators/filter.cjs");
|
|
9
10
|
const negate = require("./operators/negate.cjs");
|
|
10
11
|
const concat = require("./operators/concat.cjs");
|
|
@@ -29,6 +30,8 @@ exports.groupByOperators = groupBy.groupByOperators;
|
|
|
29
30
|
exports.pipe = pipe.pipe;
|
|
30
31
|
exports.MapOperator = map.MapOperator;
|
|
31
32
|
exports.map = map.map;
|
|
33
|
+
exports.TapOperator = tap.TapOperator;
|
|
34
|
+
exports.tap = tap.tap;
|
|
32
35
|
exports.FilterOperator = filter.FilterOperator;
|
|
33
36
|
exports.filter = filter.filter;
|
|
34
37
|
exports.NegateOperator = negate.NegateOperator;
|
package/dist/cjs/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -70,6 +70,7 @@ function orderByWithIndex(valueExtractor, options) {
|
|
|
70
70
|
function orderByWithFractionalIndexBase(topKFunction, valueExtractor, options) {
|
|
71
71
|
const limit = (options == null ? void 0 : options.limit) ?? Infinity;
|
|
72
72
|
const offset = (options == null ? void 0 : options.offset) ?? 0;
|
|
73
|
+
const setSizeCallback = options == null ? void 0 : options.setSizeCallback;
|
|
73
74
|
const comparator = (options == null ? void 0 : options.comparator) ?? ((a, b) => {
|
|
74
75
|
if (a === b) return 0;
|
|
75
76
|
if (a < b) return -1;
|
|
@@ -81,7 +82,8 @@ function orderByWithFractionalIndexBase(topKFunction, valueExtractor, options) {
|
|
|
81
82
|
(a, b) => comparator(valueExtractor(a), valueExtractor(b)),
|
|
82
83
|
{
|
|
83
84
|
limit,
|
|
84
|
-
offset
|
|
85
|
+
offset,
|
|
86
|
+
setSizeCallback
|
|
85
87
|
}
|
|
86
88
|
),
|
|
87
89
|
consolidate.consolidate()
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orderBy.cjs","sources":["../../../src/operators/orderBy.ts"],"sourcesContent":["import { topK, topKWithIndex } from \"./topK.js\"\nimport { topKWithFractionalIndex } from \"./topKWithFractionalIndex.js\"\nimport { map } from \"./map.js\"\nimport { innerJoin } from \"./join.js\"\nimport { consolidate } from \"./consolidate.js\"\nimport type { KeyValue } from \"../types.js\"\nimport type { IStreamBuilder } from \"../types\"\n\nexport interface OrderByOptions<Ve> {\n comparator?: (a: Ve, b: Ve) => number\n limit?: number\n offset?: number\n}\n\n/**\n * Orders the elements and limits the number of results, with optional offset\n * This requires a keyed stream, and uses the `topK` operator to order all the elements.\n *\n * @param valueExtractor - A function that extracts the value to order by from the element\n * @param options - An optional object containing comparator, limit and offset properties\n * @returns A piped operator that orders the elements and limits the number of results\n */\nexport function orderBy<T extends KeyValue<unknown, unknown>, Ve = unknown>(\n valueExtractor: (\n value: T extends KeyValue<unknown, infer V> ? V : never\n ) => Ve,\n options?: OrderByOptions<Ve>\n) {\n const limit = options?.limit ?? Infinity\n const offset = options?.offset ?? 0\n const comparator =\n options?.comparator ??\n ((a, b) => {\n // Default to JS like ordering\n if (a === b) return 0\n if (a < b) return -1\n return 1\n })\n\n return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {\n type KeyType = T extends KeyValue<infer K, unknown> ? K : never\n\n return stream.pipe(\n map(\n ([key, value]) =>\n [\n null,\n [\n key,\n valueExtractor(\n value as T extends KeyValue<unknown, infer V> ? V : never\n ),\n ],\n ] as KeyValue<null, [KeyType, Ve]>\n ),\n topK((a, b) => comparator(a[1], b[1]), { limit, offset }),\n map(([_, [key]]) => [key, null] as KeyValue<KeyType, null>),\n innerJoin(stream),\n map(([key, value]) => {\n return [key, value[1]] as T\n }),\n consolidate()\n )\n }\n}\n\n/**\n * Orders the elements and limits the number of results, with optional offset and\n * annotates the value with the index.\n * This requires a keyed stream, and uses the `topKWithIndex` operator to order all the elements.\n *\n * @param valueExtractor - A function that extracts the value to order by from the element\n * @param options - An optional object containing comparator, limit and offset properties\n * @returns A piped operator that orders the elements and limits the number of results\n */\nexport function orderByWithIndex<\n T extends KeyValue<unknown, unknown>,\n Ve = unknown,\n>(\n valueExtractor: (\n value: T extends KeyValue<unknown, infer V> ? V : never\n ) => Ve,\n options?: OrderByOptions<Ve>\n) {\n const limit = options?.limit ?? Infinity\n const offset = options?.offset ?? 0\n const comparator =\n options?.comparator ??\n ((a, b) => {\n // Default to JS like ordering\n if (a === b) return 0\n if (a < b) return -1\n return 1\n })\n\n return (\n stream: IStreamBuilder<T>\n ): IStreamBuilder<\n KeyValue<\n T extends KeyValue<infer K, unknown> ? K : never,\n [T extends KeyValue<unknown, infer V> ? V : never, number]\n >\n > => {\n type KeyType = T extends KeyValue<infer K, unknown> ? K : never\n type ValueType = T extends KeyValue<unknown, infer V> ? V : never\n\n return stream.pipe(\n map(\n ([key, value]) =>\n [\n null,\n [\n key,\n valueExtractor(\n value as T extends KeyValue<unknown, infer V> ? V : never\n ),\n ],\n ] as KeyValue<null, [KeyType, Ve]>\n ),\n topKWithIndex((a, b) => comparator(a[1], b[1]), { limit, offset }),\n map(([_, [[key], index]]) => [key, index] as KeyValue<KeyType, number>),\n innerJoin(stream),\n map(([key, [index, value]]) => {\n return [key, [value, index]] as KeyValue<KeyType, [ValueType, number]>\n }),\n consolidate()\n )\n }\n}\n\nexport function orderByWithFractionalIndexBase<\n T extends KeyValue<unknown, unknown>,\n Ve = unknown,\n>(\n topKFunction: typeof topKWithFractionalIndex,\n valueExtractor: (\n value: T extends KeyValue<unknown, infer V> ? V : never\n ) => Ve,\n options?:
|
|
1
|
+
{"version":3,"file":"orderBy.cjs","sources":["../../../src/operators/orderBy.ts"],"sourcesContent":["import { topK, topKWithIndex } from \"./topK.js\"\nimport { topKWithFractionalIndex } from \"./topKWithFractionalIndex.js\"\nimport { map } from \"./map.js\"\nimport { innerJoin } from \"./join.js\"\nimport { consolidate } from \"./consolidate.js\"\nimport type { KeyValue } from \"../types.js\"\nimport type { IStreamBuilder } from \"../types\"\n\nexport interface OrderByOptions<Ve> {\n comparator?: (a: Ve, b: Ve) => number\n limit?: number\n offset?: number\n}\n\ntype OrderByWithFractionalIndexOptions<Ve> = OrderByOptions<Ve> & {\n setSizeCallback?: (getSize: () => number) => void\n}\n\n/**\n * Orders the elements and limits the number of results, with optional offset\n * This requires a keyed stream, and uses the `topK` operator to order all the elements.\n *\n * @param valueExtractor - A function that extracts the value to order by from the element\n * @param options - An optional object containing comparator, limit and offset properties\n * @returns A piped operator that orders the elements and limits the number of results\n */\nexport function orderBy<T extends KeyValue<unknown, unknown>, Ve = unknown>(\n valueExtractor: (\n value: T extends KeyValue<unknown, infer V> ? V : never\n ) => Ve,\n options?: OrderByOptions<Ve>\n) {\n const limit = options?.limit ?? Infinity\n const offset = options?.offset ?? 0\n const comparator =\n options?.comparator ??\n ((a, b) => {\n // Default to JS like ordering\n if (a === b) return 0\n if (a < b) return -1\n return 1\n })\n\n return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {\n type KeyType = T extends KeyValue<infer K, unknown> ? K : never\n\n return stream.pipe(\n map(\n ([key, value]) =>\n [\n null,\n [\n key,\n valueExtractor(\n value as T extends KeyValue<unknown, infer V> ? V : never\n ),\n ],\n ] as KeyValue<null, [KeyType, Ve]>\n ),\n topK((a, b) => comparator(a[1], b[1]), { limit, offset }),\n map(([_, [key]]) => [key, null] as KeyValue<KeyType, null>),\n innerJoin(stream),\n map(([key, value]) => {\n return [key, value[1]] as T\n }),\n consolidate()\n )\n }\n}\n\n/**\n * Orders the elements and limits the number of results, with optional offset and\n * annotates the value with the index.\n * This requires a keyed stream, and uses the `topKWithIndex` operator to order all the elements.\n *\n * @param valueExtractor - A function that extracts the value to order by from the element\n * @param options - An optional object containing comparator, limit and offset properties\n * @returns A piped operator that orders the elements and limits the number of results\n */\nexport function orderByWithIndex<\n T extends KeyValue<unknown, unknown>,\n Ve = unknown,\n>(\n valueExtractor: (\n value: T extends KeyValue<unknown, infer V> ? V : never\n ) => Ve,\n options?: OrderByOptions<Ve>\n) {\n const limit = options?.limit ?? Infinity\n const offset = options?.offset ?? 0\n const comparator =\n options?.comparator ??\n ((a, b) => {\n // Default to JS like ordering\n if (a === b) return 0\n if (a < b) return -1\n return 1\n })\n\n return (\n stream: IStreamBuilder<T>\n ): IStreamBuilder<\n KeyValue<\n T extends KeyValue<infer K, unknown> ? K : never,\n [T extends KeyValue<unknown, infer V> ? V : never, number]\n >\n > => {\n type KeyType = T extends KeyValue<infer K, unknown> ? K : never\n type ValueType = T extends KeyValue<unknown, infer V> ? V : never\n\n return stream.pipe(\n map(\n ([key, value]) =>\n [\n null,\n [\n key,\n valueExtractor(\n value as T extends KeyValue<unknown, infer V> ? V : never\n ),\n ],\n ] as KeyValue<null, [KeyType, Ve]>\n ),\n topKWithIndex((a, b) => comparator(a[1], b[1]), { limit, offset }),\n map(([_, [[key], index]]) => [key, index] as KeyValue<KeyType, number>),\n innerJoin(stream),\n map(([key, [index, value]]) => {\n return [key, [value, index]] as KeyValue<KeyType, [ValueType, number]>\n }),\n consolidate()\n )\n }\n}\n\nexport function orderByWithFractionalIndexBase<\n T extends KeyValue<unknown, unknown>,\n Ve = unknown,\n>(\n topKFunction: typeof topKWithFractionalIndex,\n valueExtractor: (\n value: T extends KeyValue<unknown, infer V> ? V : never\n ) => Ve,\n options?: OrderByWithFractionalIndexOptions<Ve>\n) {\n type KeyType = T extends KeyValue<infer K, unknown> ? K : never\n type ValueType = T extends KeyValue<unknown, infer V> ? V : never\n\n const limit = options?.limit ?? Infinity\n const offset = options?.offset ?? 0\n const setSizeCallback = options?.setSizeCallback\n const comparator =\n options?.comparator ??\n ((a, b) => {\n // Default to JS like ordering\n if (a === b) return 0\n if (a < b) return -1\n return 1\n })\n\n return (\n stream: IStreamBuilder<T>\n ): IStreamBuilder<[KeyType, [ValueType, string]]> => {\n return stream.pipe(\n topKFunction(\n (a: ValueType, b: ValueType) =>\n comparator(valueExtractor(a), valueExtractor(b)),\n {\n limit,\n offset,\n setSizeCallback,\n }\n ),\n consolidate()\n )\n }\n}\n\n/**\n * Orders the elements and limits the number of results, with optional offset and\n * annotates the value with a fractional index.\n * This requires a keyed stream, and uses the `topKWithFractionalIndex` operator to order all the elements.\n *\n * @param valueExtractor - A function that extracts the value to order by from the element\n * @param options - An optional object containing comparator, limit and offset properties\n * @returns A piped operator that orders the elements and limits the number of results\n */\nexport function orderByWithFractionalIndex<\n T extends KeyValue<unknown, unknown>,\n Ve = unknown,\n>(\n valueExtractor: (\n value: T extends KeyValue<unknown, infer V> ? V : never\n ) => Ve,\n options?: OrderByWithFractionalIndexOptions<Ve>\n) {\n return orderByWithFractionalIndexBase(\n topKWithFractionalIndex,\n valueExtractor,\n options\n )\n}\n"],"names":["map","topK","innerJoin","consolidate","topKWithIndex","topKWithFractionalIndex"],"mappings":"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"}
|
|
@@ -6,6 +6,9 @@ export interface OrderByOptions<Ve> {
|
|
|
6
6
|
limit?: number;
|
|
7
7
|
offset?: number;
|
|
8
8
|
}
|
|
9
|
+
type OrderByWithFractionalIndexOptions<Ve> = OrderByOptions<Ve> & {
|
|
10
|
+
setSizeCallback?: (getSize: () => number) => void;
|
|
11
|
+
};
|
|
9
12
|
/**
|
|
10
13
|
* Orders the elements and limits the number of results, with optional offset
|
|
11
14
|
* This requires a keyed stream, and uses the `topK` operator to order all the elements.
|
|
@@ -25,7 +28,7 @@ export declare function orderBy<T extends KeyValue<unknown, unknown>, Ve = unkno
|
|
|
25
28
|
* @returns A piped operator that orders the elements and limits the number of results
|
|
26
29
|
*/
|
|
27
30
|
export declare function orderByWithIndex<T extends KeyValue<unknown, unknown>, Ve = unknown>(valueExtractor: (value: T extends KeyValue<unknown, infer V> ? V : never) => Ve, options?: OrderByOptions<Ve>): (stream: IStreamBuilder<T>) => IStreamBuilder<KeyValue<T extends KeyValue<infer K, unknown> ? K : never, [T extends KeyValue<unknown, infer V> ? V : never, number]>>;
|
|
28
|
-
export declare function orderByWithFractionalIndexBase<T extends KeyValue<unknown, unknown>, Ve = unknown>(topKFunction: typeof topKWithFractionalIndex, valueExtractor: (value: T extends KeyValue<unknown, infer V> ? V : never) => Ve, options?:
|
|
31
|
+
export declare function orderByWithFractionalIndexBase<T extends KeyValue<unknown, unknown>, Ve = unknown>(topKFunction: typeof topKWithFractionalIndex, valueExtractor: (value: T extends KeyValue<unknown, infer V> ? V : never) => Ve, options?: OrderByWithFractionalIndexOptions<Ve>): (stream: IStreamBuilder<T>) => IStreamBuilder<[T extends KeyValue<infer K, unknown> ? K : never, [T extends KeyValue<unknown, infer V> ? V : never, string]]>;
|
|
29
32
|
/**
|
|
30
33
|
* Orders the elements and limits the number of results, with optional offset and
|
|
31
34
|
* annotates the value with a fractional index.
|
|
@@ -35,4 +38,5 @@ export declare function orderByWithFractionalIndexBase<T extends KeyValue<unknow
|
|
|
35
38
|
* @param options - An optional object containing comparator, limit and offset properties
|
|
36
39
|
* @returns A piped operator that orders the elements and limits the number of results
|
|
37
40
|
*/
|
|
38
|
-
export declare function orderByWithFractionalIndex<T extends KeyValue<unknown, unknown>, Ve = unknown>(valueExtractor: (value: T extends KeyValue<unknown, infer V> ? V : never) => Ve, options?:
|
|
41
|
+
export declare function orderByWithFractionalIndex<T extends KeyValue<unknown, unknown>, Ve = unknown>(valueExtractor: (value: T extends KeyValue<unknown, infer V> ? V : never) => Ve, options?: OrderByWithFractionalIndexOptions<Ve>): (stream: IStreamBuilder<T>) => IStreamBuilder<[T extends KeyValue<infer K, unknown> ? K : never, [T extends KeyValue<unknown, infer V> ? V : never, string]]>;
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __typeError = (msg) => {
|
|
3
|
+
throw TypeError(msg);
|
|
4
|
+
};
|
|
5
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
6
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
7
|
+
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
8
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
9
|
+
var _f;
|
|
10
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
11
|
+
const graph = require("../graph.cjs");
|
|
12
|
+
const d2 = require("../d2.cjs");
|
|
13
|
+
class TapOperator extends graph.LinearUnaryOperator {
|
|
14
|
+
constructor(id, inputA, output, f) {
|
|
15
|
+
super(id, inputA, output);
|
|
16
|
+
__privateAdd(this, _f);
|
|
17
|
+
__privateSet(this, _f, f);
|
|
18
|
+
}
|
|
19
|
+
inner(collection) {
|
|
20
|
+
return collection.map((data) => {
|
|
21
|
+
__privateGet(this, _f).call(this, data);
|
|
22
|
+
return data;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
_f = new WeakMap();
|
|
27
|
+
function tap(f) {
|
|
28
|
+
return (stream) => {
|
|
29
|
+
const output = new d2.StreamBuilder(
|
|
30
|
+
stream.graph,
|
|
31
|
+
new graph.DifferenceStreamWriter()
|
|
32
|
+
);
|
|
33
|
+
const operator = new TapOperator(
|
|
34
|
+
stream.graph.getNextOperatorId(),
|
|
35
|
+
stream.connectReader(),
|
|
36
|
+
output.writer,
|
|
37
|
+
f
|
|
38
|
+
);
|
|
39
|
+
stream.graph.addOperator(operator);
|
|
40
|
+
stream.graph.addStream(output.connectReader());
|
|
41
|
+
return output;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
exports.TapOperator = TapOperator;
|
|
45
|
+
exports.tap = tap;
|
|
46
|
+
//# sourceMappingURL=tap.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tap.cjs","sources":["../../../src/operators/tap.ts"],"sourcesContent":["import { DifferenceStreamWriter, LinearUnaryOperator } from \"../graph.js\"\nimport { StreamBuilder } from \"../d2.js\"\nimport type { IStreamBuilder, PipedOperator } from \"../types.js\"\nimport type { DifferenceStreamReader } from \"../graph.js\"\nimport type { MultiSet } from \"../multiset.js\"\n\n/**\n * Operator that applies a function to each element in the input stream\n */\nexport class TapOperator<T> extends LinearUnaryOperator<T, T> {\n #f: (data: T) => void\n\n constructor(\n id: number,\n inputA: DifferenceStreamReader<T>,\n output: DifferenceStreamWriter<T>,\n f: (data: T) => void\n ) {\n super(id, inputA, output)\n this.#f = f\n }\n\n inner(collection: MultiSet<T>): MultiSet<T> {\n return collection.map((data) => {\n this.#f(data)\n return data\n })\n }\n}\n\n/**\n * Invokes a function for each element in the input stream.\n * This operator doesn't modify the stream and is used to perform side effects.\n * @param f - The function to invoke on each element\n * @returns The input stream\n */\nexport function tap<T>(f: (data: T) => void): PipedOperator<T, T> {\n return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {\n const output = new StreamBuilder<T>(\n stream.graph,\n new DifferenceStreamWriter<T>()\n )\n const operator = new TapOperator<T>(\n stream.graph.getNextOperatorId(),\n stream.connectReader(),\n output.writer,\n f\n )\n stream.graph.addOperator(operator)\n stream.graph.addStream(output.connectReader())\n return output\n }\n}\n"],"names":["LinearUnaryOperator","StreamBuilder","DifferenceStreamWriter"],"mappings":";;;;;;;;;;;;AASO,MAAM,oBAAuBA,MAAAA,oBAA0B;AAAA,EAG5D,YACE,IACA,QACA,QACA,GACA;AACA,UAAM,IAAI,QAAQ,MAAM;AAR1B;AASE,uBAAK,IAAK;AAAA,EACZ;AAAA,EAEA,MAAM,YAAsC;AAC1C,WAAO,WAAW,IAAI,CAAC,SAAS;AAC9B,yBAAK,IAAL,WAAQ;AACR,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AACF;AAlBE;AA0BK,SAAS,IAAO,GAA2C;AAChE,SAAO,CAAC,WAAiD;AACvD,UAAM,SAAS,IAAIC,GAAAA;AAAAA,MACjB,OAAO;AAAA,MACP,IAAIC,MAAAA,uBAAA;AAAA,IAA0B;AAEhC,UAAM,WAAW,IAAI;AAAA,MACnB,OAAO,MAAM,kBAAA;AAAA,MACb,OAAO,cAAA;AAAA,MACP,OAAO;AAAA,MACP;AAAA,IAAA;AAEF,WAAO,MAAM,YAAY,QAAQ;AACjC,WAAO,MAAM,UAAU,OAAO,cAAA,CAAe;AAC7C,WAAO;AAAA,EACT;AACF;;;"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { DifferenceStreamWriter, LinearUnaryOperator, DifferenceStreamReader } from '../graph.js';
|
|
2
|
+
import { PipedOperator } from '../types.js';
|
|
3
|
+
import { MultiSet } from '../multiset.js';
|
|
4
|
+
/**
|
|
5
|
+
* Operator that applies a function to each element in the input stream
|
|
6
|
+
*/
|
|
7
|
+
export declare class TapOperator<T> extends LinearUnaryOperator<T, T> {
|
|
8
|
+
#private;
|
|
9
|
+
constructor(id: number, inputA: DifferenceStreamReader<T>, output: DifferenceStreamWriter<T>, f: (data: T) => void);
|
|
10
|
+
inner(collection: MultiSet<T>): MultiSet<T>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Invokes a function for each element in the input stream.
|
|
14
|
+
* This operator doesn't modify the stream and is used to perform side effects.
|
|
15
|
+
* @param f - The function to invoke on each element
|
|
16
|
+
* @returns The input stream
|
|
17
|
+
*/
|
|
18
|
+
export declare function tap<T>(f: (data: T) => void): PipedOperator<T, T>;
|
|
@@ -7,7 +7,7 @@ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read fr
|
|
|
7
7
|
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
8
8
|
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
9
9
|
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
10
|
-
var _sortedValues, _comparator, _topKStart, _topKEnd, _TopKArray_instances, findIndex_fn, _index, _topK;
|
|
10
|
+
var _sortedValues, _comparator, _topKStart, _topKEnd, _TopKArray_instances, findIndex_fn, _index, _topK, _limit;
|
|
11
11
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
12
12
|
const fractionalIndexing = require("fractional-indexing");
|
|
13
13
|
const graph = require("../graph.cjs");
|
|
@@ -25,6 +25,12 @@ class TopKArray {
|
|
|
25
25
|
__privateSet(this, _topKEnd, offset + limit);
|
|
26
26
|
__privateSet(this, _comparator, comparator);
|
|
27
27
|
}
|
|
28
|
+
get size() {
|
|
29
|
+
const offset = __privateGet(this, _topKStart);
|
|
30
|
+
const limit = __privateGet(this, _topKEnd) - __privateGet(this, _topKStart);
|
|
31
|
+
const available = __privateGet(this, _sortedValues).length - offset;
|
|
32
|
+
return Math.max(0, Math.min(limit, available));
|
|
33
|
+
}
|
|
28
34
|
insert(value) {
|
|
29
35
|
const result = { moveIn: null, moveOut: null };
|
|
30
36
|
const index = __privateMethod(this, _TopKArray_instances, findIndex_fn).call(this, value);
|
|
@@ -89,6 +95,7 @@ findIndex_fn = function(value) {
|
|
|
89
95
|
};
|
|
90
96
|
class TopKWithFractionalIndexOperator extends graph.UnaryOperator {
|
|
91
97
|
constructor(id, inputA, output, comparator, options) {
|
|
98
|
+
var _a;
|
|
92
99
|
super(id, inputA, output);
|
|
93
100
|
__privateAdd(this, _index, /* @__PURE__ */ new Map());
|
|
94
101
|
// maps keys to their multiplicity
|
|
@@ -97,7 +104,8 @@ class TopKWithFractionalIndexOperator extends graph.UnaryOperator {
|
|
|
97
104
|
* and returns changes to the topK.
|
|
98
105
|
*/
|
|
99
106
|
__privateAdd(this, _topK);
|
|
100
|
-
|
|
107
|
+
__privateAdd(this, _limit);
|
|
108
|
+
__privateSet(this, _limit, options.limit ?? Infinity);
|
|
101
109
|
const offset = options.offset ?? 0;
|
|
102
110
|
const compareTaggedValues = (a, b) => {
|
|
103
111
|
const valueComparison = comparator(getVal(a), getVal(b));
|
|
@@ -108,7 +116,8 @@ class TopKWithFractionalIndexOperator extends graph.UnaryOperator {
|
|
|
108
116
|
const tieBreakerB = getTag(b);
|
|
109
117
|
return tieBreakerA - tieBreakerB;
|
|
110
118
|
};
|
|
111
|
-
__privateSet(this, _topK, this.createTopK(offset,
|
|
119
|
+
__privateSet(this, _topK, this.createTopK(offset, __privateGet(this, _limit), compareTaggedValues));
|
|
120
|
+
(_a = options.setSizeCallback) == null ? void 0 : _a.call(options, () => __privateGet(this, _topK).size);
|
|
112
121
|
}
|
|
113
122
|
createTopK(offset, limit, comparator) {
|
|
114
123
|
return new TopKArray(offset, limit, comparator);
|
|
@@ -170,6 +179,7 @@ class TopKWithFractionalIndexOperator extends graph.UnaryOperator {
|
|
|
170
179
|
}
|
|
171
180
|
_index = new WeakMap();
|
|
172
181
|
_topK = new WeakMap();
|
|
182
|
+
_limit = new WeakMap();
|
|
173
183
|
function topKWithFractionalIndex(comparator, options) {
|
|
174
184
|
const opts = options || {};
|
|
175
185
|
return (stream) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"topKWithFractionalIndex.cjs","sources":["../../../src/operators/topKWithFractionalIndex.ts"],"sourcesContent":["import { generateKeyBetween } from \"fractional-indexing\"\nimport { DifferenceStreamWriter, UnaryOperator } from \"../graph.js\"\nimport { StreamBuilder } from \"../d2.js\"\nimport { MultiSet } from \"../multiset.js\"\nimport { binarySearch, globalObjectIdGenerator } from \"../utils.js\"\nimport type { DifferenceStreamReader } from \"../graph.js\"\nimport type { IStreamBuilder, PipedOperator } from \"../types.js\"\n\nexport interface TopKWithFractionalIndexOptions {\n limit?: number\n offset?: number\n}\n\nexport type TopKChanges<V> = {\n /** Indicates which element moves into the topK (if any) */\n moveIn: IndexedValue<V> | null\n /** Indicates which element moves out of the topK (if any) */\n moveOut: IndexedValue<V> | null\n}\n\n/**\n * A topK data structure that supports insertions and deletions\n * and returns changes to the topK.\n */\nexport interface TopK<V> {\n insert: (value: V) => TopKChanges<V>\n delete: (value: V) => TopKChanges<V>\n}\n\n/**\n * Implementation of a topK data structure.\n * Uses a sorted array internally to store the values and keeps a topK window over that array.\n * Inserts and deletes are O(n) operations because worst case an element is inserted/deleted\n * at the start of the array which causes all the elements to shift to the right/left.\n */\nclass TopKArray<V> implements TopK<V> {\n #sortedValues: Array<IndexedValue<V>> = []\n #comparator: (a: V, b: V) => number\n #topKStart: number\n #topKEnd: number\n\n constructor(\n offset: number,\n limit: number,\n comparator: (a: V, b: V) => number\n ) {\n this.#topKStart = offset\n this.#topKEnd = offset + limit\n this.#comparator = comparator\n }\n\n insert(value: V): TopKChanges<V> {\n const result: TopKChanges<V> = { moveIn: null, moveOut: null }\n\n // Lookup insert position\n const index = this.#findIndex(value)\n // Generate fractional index based on the fractional indices of the elements before and after it\n const indexBefore =\n index === 0 ? null : getIndex(this.#sortedValues[index - 1]!)\n const indexAfter =\n index === this.#sortedValues.length\n ? null\n : getIndex(this.#sortedValues[index]!)\n const fractionalIndex = generateKeyBetween(indexBefore, indexAfter)\n\n // Insert the value at the correct position\n const val = indexedValue(value, fractionalIndex)\n // Splice is O(n) where n = all elements in the collection (i.e. n >= k) !\n this.#sortedValues.splice(index, 0, val)\n\n // Check if the topK changed\n if (index < this.#topKEnd) {\n // The inserted element is either before the top K or within the top K\n // If it is before the top K then it moves the element that was right before the topK into the topK\n // If it is within the top K then the inserted element moves into the top K\n // In both cases the last element of the old top K now moves out of the top K\n const moveInIndex = Math.max(index, this.#topKStart)\n if (moveInIndex < this.#sortedValues.length) {\n // We actually have a topK\n // because in some cases there may not be enough elements in the array to reach the start of the topK\n // e.g. [1, 2, 3] with K = 2 and offset = 3 does not have a topK\n result.moveIn = this.#sortedValues[moveInIndex]!\n\n // We need to remove the element that falls out of the top K\n // The element that falls out of the top K has shifted one to the right\n // because of the element we inserted, so we find it at index topKEnd\n if (this.#topKEnd < this.#sortedValues.length) {\n result.moveOut = this.#sortedValues[this.#topKEnd]!\n }\n }\n }\n\n return result\n }\n\n /**\n * Deletes a value that may or may not be in the topK.\n * IMPORTANT: this assumes that the value is present in the collection\n * if it's not the case it will remove the element\n * that is on the position where the provided `value` would be.\n */\n delete(value: V): TopKChanges<V> {\n const result: TopKChanges<V> = { moveIn: null, moveOut: null }\n\n // Lookup delete position\n const index = this.#findIndex(value)\n // Remove the value at that position\n const [removedElem] = this.#sortedValues.splice(index, 1)\n\n // Check if the topK changed\n if (index < this.#topKEnd) {\n // The removed element is either before the top K or within the top K\n // If it is before the top K then the first element of the topK moves out of the topK\n // If it is within the top K then the removed element moves out of the topK\n result.moveOut = removedElem!\n if (index < this.#topKStart) {\n // The removed element is before the topK\n // so actually, the first element of the topK moves out of the topK\n // and not the element that we removed\n // The first element of the topK is now at index topKStart - 1\n // since we removed an element before the topK\n const moveOutIndex = this.#topKStart - 1\n if (moveOutIndex < this.#sortedValues.length) {\n result.moveOut = this.#sortedValues[moveOutIndex]!\n } else {\n // No value is moving out of the topK\n // because there are no elements in the topK\n result.moveOut = null\n }\n }\n\n // Since we removed an element that was before or in the topK\n // the first element after the topK moved one position to the left\n // and thus falls into the topK now\n const moveInIndex = this.#topKEnd - 1\n if (moveInIndex < this.#sortedValues.length) {\n result.moveIn = this.#sortedValues[moveInIndex]!\n }\n }\n\n return result\n }\n\n // TODO: see if there is a way to refactor the code for insert and delete in the topK above\n // because they are very similar, one is shifting the topK window to the left and the other is shifting it to the right\n // so i have the feeling there is a common pattern here and we can implement both cases using that pattern\n\n #findIndex(value: V): number {\n return binarySearch(this.#sortedValues, indexedValue(value, ``), (a, b) =>\n this.#comparator(getValue(a), getValue(b))\n )\n }\n}\n\n/**\n * Operator for fractional indexed topK operations\n * This operator maintains fractional indices for sorted elements\n * and only updates indices when elements move position\n */\nexport class TopKWithFractionalIndexOperator<K, T> extends UnaryOperator<\n [K, T],\n [K, IndexedValue<T>]\n> {\n #index: Map<K, number> = new Map() // maps keys to their multiplicity\n\n /**\n * topK data structure that supports insertions and deletions\n * and returns changes to the topK.\n */\n #topK: TopK<TaggedValue<K, T>>\n\n constructor(\n id: number,\n inputA: DifferenceStreamReader<[K, T]>,\n output: DifferenceStreamWriter<[K, IndexedValue<T>]>,\n comparator: (a: T, b: T) => number,\n options: TopKWithFractionalIndexOptions\n ) {\n super(id, inputA, output)\n const limit = options.limit ?? Infinity\n const offset = options.offset ?? 0\n const compareTaggedValues = (\n a: TaggedValue<K, T>,\n b: TaggedValue<K, T>\n ) => {\n // First compare on the value\n const valueComparison = comparator(getVal(a), getVal(b))\n if (valueComparison !== 0) {\n return valueComparison\n }\n // If the values are equal, compare on the tag (object identity)\n const tieBreakerA = getTag(a)\n const tieBreakerB = getTag(b)\n return tieBreakerA - tieBreakerB\n }\n this.#topK = this.createTopK(offset, limit, compareTaggedValues)\n }\n\n protected createTopK(\n offset: number,\n limit: number,\n comparator: (a: TaggedValue<K, T>, b: TaggedValue<K, T>) => number\n ): TopK<TaggedValue<K, T>> {\n return new TopKArray(offset, limit, comparator)\n }\n\n run(): void {\n const result: Array<[[K, IndexedValue<T>], number]> = []\n for (const message of this.inputMessages()) {\n for (const [item, multiplicity] of message.getInner()) {\n const [key, value] = item\n this.processElement(key, value, multiplicity, result)\n }\n }\n\n if (result.length > 0) {\n this.output.sendData(new MultiSet(result))\n }\n }\n\n processElement(\n key: K,\n value: T,\n multiplicity: number,\n result: Array<[[K, IndexedValue<T>], number]>\n ): void {\n const { oldMultiplicity, newMultiplicity } = this.addKey(key, multiplicity)\n\n let res: TopKChanges<TaggedValue<K, T>> = {\n moveIn: null,\n moveOut: null,\n }\n if (oldMultiplicity <= 0 && newMultiplicity > 0) {\n // The value was invisible but should now be visible\n // Need to insert it into the array of sorted values\n const taggedValue = tagValue(key, value)\n res = this.#topK.insert(taggedValue)\n } else if (oldMultiplicity > 0 && newMultiplicity <= 0) {\n // The value was visible but should now be invisible\n // Need to remove it from the array of sorted values\n const taggedValue = tagValue(key, value)\n res = this.#topK.delete(taggedValue)\n } else {\n // The value was invisible and it remains invisible\n // or it was visible and remains visible\n // so it doesn't affect the topK\n }\n\n if (res.moveIn) {\n const index = getIndex(res.moveIn)\n const taggedValue = getValue(res.moveIn)\n const k = getKey(taggedValue)\n const val = getVal(taggedValue)\n result.push([[k, [val, index]], 1])\n }\n\n if (res.moveOut) {\n const index = getIndex(res.moveOut)\n const taggedValue = getValue(res.moveOut)\n const k = getKey(taggedValue)\n const val = getVal(taggedValue)\n result.push([[k, [val, index]], -1])\n }\n\n return\n }\n\n private getMultiplicity(key: K): number {\n return this.#index.get(key) ?? 0\n }\n\n private addKey(\n key: K,\n multiplicity: number\n ): { oldMultiplicity: number; newMultiplicity: number } {\n const oldMultiplicity = this.getMultiplicity(key)\n const newMultiplicity = oldMultiplicity + multiplicity\n if (newMultiplicity === 0) {\n this.#index.delete(key)\n } else {\n this.#index.set(key, newMultiplicity)\n }\n return { oldMultiplicity, newMultiplicity }\n }\n}\n\n/**\n * Limits the number of results based on a comparator, with optional offset.\n * Uses fractional indexing to minimize the number of changes when elements move positions.\n * Each element is assigned a fractional index that is lexicographically sortable.\n * When elements move, only the indices of the moved elements are updated, not all elements.\n *\n * @param comparator - A function that compares two elements\n * @param options - An optional object containing limit and offset properties\n * @returns A piped operator that orders the elements and limits the number of results\n */\nexport function topKWithFractionalIndex<KType, T>(\n comparator: (a: T, b: T) => number,\n options?: TopKWithFractionalIndexOptions\n): PipedOperator<[KType, T], [KType, IndexedValue<T>]> {\n const opts = options || {}\n\n return (\n stream: IStreamBuilder<[KType, T]>\n ): IStreamBuilder<[KType, IndexedValue<T>]> => {\n const output = new StreamBuilder<[KType, IndexedValue<T>]>(\n stream.graph,\n new DifferenceStreamWriter<[KType, IndexedValue<T>]>()\n )\n const operator = new TopKWithFractionalIndexOperator<KType, T>(\n stream.graph.getNextOperatorId(),\n stream.connectReader(),\n output.writer,\n comparator,\n opts\n )\n stream.graph.addOperator(operator)\n stream.graph.addStream(output.connectReader())\n return output\n }\n}\n\n// Abstraction for fractionally indexed values\nexport type FractionalIndex = string\nexport type IndexedValue<V> = [V, FractionalIndex]\n\nexport function indexedValue<V>(\n value: V,\n index: FractionalIndex\n): IndexedValue<V> {\n return [value, index]\n}\n\nexport function getValue<V>(indexedVal: IndexedValue<V>): V {\n return indexedVal[0]\n}\n\nexport function getIndex<V>(indexedVal: IndexedValue<V>): FractionalIndex {\n return indexedVal[1]\n}\n\nexport type Tag = number\nexport type TaggedValue<K, V> = [K, V, Tag]\n\nfunction tagValue<K, V>(key: K, value: V): TaggedValue<K, V> {\n return [key, value, globalObjectIdGenerator.getId(key)]\n}\n\nfunction getKey<K, V>(tieBreakerTaggedValue: TaggedValue<K, V>): K {\n return tieBreakerTaggedValue[0]\n}\n\nfunction getVal<K, V>(tieBreakerTaggedValue: TaggedValue<K, V>): V {\n return tieBreakerTaggedValue[1]\n}\n\nfunction getTag<K, V>(tieBreakerTaggedValue: TaggedValue<K, V>): Tag {\n return tieBreakerTaggedValue[2]\n}\n"],"names":["generateKeyBetween","binarySearch","UnaryOperator","MultiSet","StreamBuilder","DifferenceStreamWriter","globalObjectIdGenerator"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"topKWithFractionalIndex.cjs","sources":["../../../src/operators/topKWithFractionalIndex.ts"],"sourcesContent":["import { generateKeyBetween } from \"fractional-indexing\"\nimport { DifferenceStreamWriter, UnaryOperator } from \"../graph.js\"\nimport { StreamBuilder } from \"../d2.js\"\nimport { MultiSet } from \"../multiset.js\"\nimport { binarySearch, globalObjectIdGenerator } from \"../utils.js\"\nimport type { DifferenceStreamReader } from \"../graph.js\"\nimport type { IStreamBuilder, PipedOperator } from \"../types.js\"\n\nexport interface TopKWithFractionalIndexOptions {\n limit?: number\n offset?: number\n setSizeCallback?: (getSize: () => number) => void\n}\n\nexport type TopKChanges<V> = {\n /** Indicates which element moves into the topK (if any) */\n moveIn: IndexedValue<V> | null\n /** Indicates which element moves out of the topK (if any) */\n moveOut: IndexedValue<V> | null\n}\n\n/**\n * A topK data structure that supports insertions and deletions\n * and returns changes to the topK.\n */\nexport interface TopK<V> {\n size: number\n insert: (value: V) => TopKChanges<V>\n delete: (value: V) => TopKChanges<V>\n}\n\n/**\n * Implementation of a topK data structure.\n * Uses a sorted array internally to store the values and keeps a topK window over that array.\n * Inserts and deletes are O(n) operations because worst case an element is inserted/deleted\n * at the start of the array which causes all the elements to shift to the right/left.\n */\nclass TopKArray<V> implements TopK<V> {\n #sortedValues: Array<IndexedValue<V>> = []\n #comparator: (a: V, b: V) => number\n #topKStart: number\n #topKEnd: number\n\n constructor(\n offset: number,\n limit: number,\n comparator: (a: V, b: V) => number\n ) {\n this.#topKStart = offset\n this.#topKEnd = offset + limit\n this.#comparator = comparator\n }\n\n get size(): number {\n const offset = this.#topKStart\n const limit = this.#topKEnd - this.#topKStart\n const available = this.#sortedValues.length - offset\n return Math.max(0, Math.min(limit, available))\n }\n\n insert(value: V): TopKChanges<V> {\n const result: TopKChanges<V> = { moveIn: null, moveOut: null }\n\n // Lookup insert position\n const index = this.#findIndex(value)\n // Generate fractional index based on the fractional indices of the elements before and after it\n const indexBefore =\n index === 0 ? null : getIndex(this.#sortedValues[index - 1]!)\n const indexAfter =\n index === this.#sortedValues.length\n ? null\n : getIndex(this.#sortedValues[index]!)\n const fractionalIndex = generateKeyBetween(indexBefore, indexAfter)\n\n // Insert the value at the correct position\n const val = indexedValue(value, fractionalIndex)\n // Splice is O(n) where n = all elements in the collection (i.e. n >= k) !\n this.#sortedValues.splice(index, 0, val)\n\n // Check if the topK changed\n if (index < this.#topKEnd) {\n // The inserted element is either before the top K or within the top K\n // If it is before the top K then it moves the element that was right before the topK into the topK\n // If it is within the top K then the inserted element moves into the top K\n // In both cases the last element of the old top K now moves out of the top K\n const moveInIndex = Math.max(index, this.#topKStart)\n if (moveInIndex < this.#sortedValues.length) {\n // We actually have a topK\n // because in some cases there may not be enough elements in the array to reach the start of the topK\n // e.g. [1, 2, 3] with K = 2 and offset = 3 does not have a topK\n result.moveIn = this.#sortedValues[moveInIndex]!\n\n // We need to remove the element that falls out of the top K\n // The element that falls out of the top K has shifted one to the right\n // because of the element we inserted, so we find it at index topKEnd\n if (this.#topKEnd < this.#sortedValues.length) {\n result.moveOut = this.#sortedValues[this.#topKEnd]!\n }\n }\n }\n\n return result\n }\n\n /**\n * Deletes a value that may or may not be in the topK.\n * IMPORTANT: this assumes that the value is present in the collection\n * if it's not the case it will remove the element\n * that is on the position where the provided `value` would be.\n */\n delete(value: V): TopKChanges<V> {\n const result: TopKChanges<V> = { moveIn: null, moveOut: null }\n\n // Lookup delete position\n const index = this.#findIndex(value)\n // Remove the value at that position\n const [removedElem] = this.#sortedValues.splice(index, 1)\n\n // Check if the topK changed\n if (index < this.#topKEnd) {\n // The removed element is either before the top K or within the top K\n // If it is before the top K then the first element of the topK moves out of the topK\n // If it is within the top K then the removed element moves out of the topK\n result.moveOut = removedElem!\n if (index < this.#topKStart) {\n // The removed element is before the topK\n // so actually, the first element of the topK moves out of the topK\n // and not the element that we removed\n // The first element of the topK is now at index topKStart - 1\n // since we removed an element before the topK\n const moveOutIndex = this.#topKStart - 1\n if (moveOutIndex < this.#sortedValues.length) {\n result.moveOut = this.#sortedValues[moveOutIndex]!\n } else {\n // No value is moving out of the topK\n // because there are no elements in the topK\n result.moveOut = null\n }\n }\n\n // Since we removed an element that was before or in the topK\n // the first element after the topK moved one position to the left\n // and thus falls into the topK now\n const moveInIndex = this.#topKEnd - 1\n if (moveInIndex < this.#sortedValues.length) {\n result.moveIn = this.#sortedValues[moveInIndex]!\n }\n }\n\n return result\n }\n\n // TODO: see if there is a way to refactor the code for insert and delete in the topK above\n // because they are very similar, one is shifting the topK window to the left and the other is shifting it to the right\n // so i have the feeling there is a common pattern here and we can implement both cases using that pattern\n\n #findIndex(value: V): number {\n return binarySearch(this.#sortedValues, indexedValue(value, ``), (a, b) =>\n this.#comparator(getValue(a), getValue(b))\n )\n }\n}\n\n/**\n * Operator for fractional indexed topK operations\n * This operator maintains fractional indices for sorted elements\n * and only updates indices when elements move position\n */\nexport class TopKWithFractionalIndexOperator<K, T> extends UnaryOperator<\n [K, T],\n [K, IndexedValue<T>]\n> {\n #index: Map<K, number> = new Map() // maps keys to their multiplicity\n\n /**\n * topK data structure that supports insertions and deletions\n * and returns changes to the topK.\n */\n #topK: TopK<TaggedValue<K, T>>\n\n #limit: number\n\n constructor(\n id: number,\n inputA: DifferenceStreamReader<[K, T]>,\n output: DifferenceStreamWriter<[K, IndexedValue<T>]>,\n comparator: (a: T, b: T) => number,\n options: TopKWithFractionalIndexOptions\n ) {\n super(id, inputA, output)\n this.#limit = options.limit ?? Infinity\n const offset = options.offset ?? 0\n const compareTaggedValues = (\n a: TaggedValue<K, T>,\n b: TaggedValue<K, T>\n ) => {\n // First compare on the value\n const valueComparison = comparator(getVal(a), getVal(b))\n if (valueComparison !== 0) {\n return valueComparison\n }\n // If the values are equal, compare on the tag (object identity)\n const tieBreakerA = getTag(a)\n const tieBreakerB = getTag(b)\n return tieBreakerA - tieBreakerB\n }\n this.#topK = this.createTopK(offset, this.#limit, compareTaggedValues)\n options.setSizeCallback?.(() => this.#topK.size)\n }\n\n protected createTopK(\n offset: number,\n limit: number,\n comparator: (a: TaggedValue<K, T>, b: TaggedValue<K, T>) => number\n ): TopK<TaggedValue<K, T>> {\n return new TopKArray(offset, limit, comparator)\n }\n\n run(): void {\n const result: Array<[[K, IndexedValue<T>], number]> = []\n for (const message of this.inputMessages()) {\n for (const [item, multiplicity] of message.getInner()) {\n const [key, value] = item\n this.processElement(key, value, multiplicity, result)\n }\n }\n\n if (result.length > 0) {\n this.output.sendData(new MultiSet(result))\n }\n }\n\n processElement(\n key: K,\n value: T,\n multiplicity: number,\n result: Array<[[K, IndexedValue<T>], number]>\n ): void {\n const { oldMultiplicity, newMultiplicity } = this.addKey(key, multiplicity)\n\n let res: TopKChanges<TaggedValue<K, T>> = {\n moveIn: null,\n moveOut: null,\n }\n if (oldMultiplicity <= 0 && newMultiplicity > 0) {\n // The value was invisible but should now be visible\n // Need to insert it into the array of sorted values\n const taggedValue = tagValue(key, value)\n res = this.#topK.insert(taggedValue)\n } else if (oldMultiplicity > 0 && newMultiplicity <= 0) {\n // The value was visible but should now be invisible\n // Need to remove it from the array of sorted values\n const taggedValue = tagValue(key, value)\n res = this.#topK.delete(taggedValue)\n } else {\n // The value was invisible and it remains invisible\n // or it was visible and remains visible\n // so it doesn't affect the topK\n }\n\n if (res.moveIn) {\n const index = getIndex(res.moveIn)\n const taggedValue = getValue(res.moveIn)\n const k = getKey(taggedValue)\n const val = getVal(taggedValue)\n result.push([[k, [val, index]], 1])\n }\n\n if (res.moveOut) {\n const index = getIndex(res.moveOut)\n const taggedValue = getValue(res.moveOut)\n const k = getKey(taggedValue)\n const val = getVal(taggedValue)\n result.push([[k, [val, index]], -1])\n }\n\n return\n }\n\n private getMultiplicity(key: K): number {\n return this.#index.get(key) ?? 0\n }\n\n private addKey(\n key: K,\n multiplicity: number\n ): { oldMultiplicity: number; newMultiplicity: number } {\n const oldMultiplicity = this.getMultiplicity(key)\n const newMultiplicity = oldMultiplicity + multiplicity\n if (newMultiplicity === 0) {\n this.#index.delete(key)\n } else {\n this.#index.set(key, newMultiplicity)\n }\n return { oldMultiplicity, newMultiplicity }\n }\n}\n\n/**\n * Limits the number of results based on a comparator, with optional offset.\n * Uses fractional indexing to minimize the number of changes when elements move positions.\n * Each element is assigned a fractional index that is lexicographically sortable.\n * When elements move, only the indices of the moved elements are updated, not all elements.\n *\n * @param comparator - A function that compares two elements\n * @param options - An optional object containing limit and offset properties\n * @returns A piped operator that orders the elements and limits the number of results\n */\nexport function topKWithFractionalIndex<KType, T>(\n comparator: (a: T, b: T) => number,\n options?: TopKWithFractionalIndexOptions\n): PipedOperator<[KType, T], [KType, IndexedValue<T>]> {\n const opts = options || {}\n\n return (\n stream: IStreamBuilder<[KType, T]>\n ): IStreamBuilder<[KType, IndexedValue<T>]> => {\n const output = new StreamBuilder<[KType, IndexedValue<T>]>(\n stream.graph,\n new DifferenceStreamWriter<[KType, IndexedValue<T>]>()\n )\n const operator = new TopKWithFractionalIndexOperator<KType, T>(\n stream.graph.getNextOperatorId(),\n stream.connectReader(),\n output.writer,\n comparator,\n opts\n )\n stream.graph.addOperator(operator)\n stream.graph.addStream(output.connectReader())\n return output\n }\n}\n\n// Abstraction for fractionally indexed values\nexport type FractionalIndex = string\nexport type IndexedValue<V> = [V, FractionalIndex]\n\nexport function indexedValue<V>(\n value: V,\n index: FractionalIndex\n): IndexedValue<V> {\n return [value, index]\n}\n\nexport function getValue<V>(indexedVal: IndexedValue<V>): V {\n return indexedVal[0]\n}\n\nexport function getIndex<V>(indexedVal: IndexedValue<V>): FractionalIndex {\n return indexedVal[1]\n}\n\nexport type Tag = number\nexport type TaggedValue<K, V> = [K, V, Tag]\n\nfunction tagValue<K, V>(key: K, value: V): TaggedValue<K, V> {\n return [key, value, globalObjectIdGenerator.getId(key)]\n}\n\nfunction getKey<K, V>(tieBreakerTaggedValue: TaggedValue<K, V>): K {\n return tieBreakerTaggedValue[0]\n}\n\nfunction getVal<K, V>(tieBreakerTaggedValue: TaggedValue<K, V>): V {\n return tieBreakerTaggedValue[1]\n}\n\nfunction getTag<K, V>(tieBreakerTaggedValue: TaggedValue<K, V>): Tag {\n return tieBreakerTaggedValue[2]\n}\n"],"names":["generateKeyBetween","binarySearch","UnaryOperator","MultiSet","StreamBuilder","DifferenceStreamWriter","globalObjectIdGenerator"],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ import { PipedOperator } from '../types.js';
|
|
|
3
3
|
export interface TopKWithFractionalIndexOptions {
|
|
4
4
|
limit?: number;
|
|
5
5
|
offset?: number;
|
|
6
|
+
setSizeCallback?: (getSize: () => number) => void;
|
|
6
7
|
}
|
|
7
8
|
export type TopKChanges<V> = {
|
|
8
9
|
/** Indicates which element moves into the topK (if any) */
|
|
@@ -15,6 +16,7 @@ export type TopKChanges<V> = {
|
|
|
15
16
|
* and returns changes to the topK.
|
|
16
17
|
*/
|
|
17
18
|
export interface TopK<V> {
|
|
19
|
+
size: number;
|
|
18
20
|
insert: (value: V) => TopKChanges<V>;
|
|
19
21
|
delete: (value: V) => TopKChanges<V>;
|
|
20
22
|
}
|
package/dist/esm/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { MultiSet } from "./multiset.js";
|
|
|
3
3
|
import { groupBy, groupByOperators } from "./operators/groupBy.js";
|
|
4
4
|
import { pipe } from "./operators/pipe.js";
|
|
5
5
|
import { MapOperator, map } from "./operators/map.js";
|
|
6
|
+
import { TapOperator, tap } from "./operators/tap.js";
|
|
6
7
|
import { FilterOperator, filter } from "./operators/filter.js";
|
|
7
8
|
import { NegateOperator, negate } from "./operators/negate.js";
|
|
8
9
|
import { ConcatOperator, concat } from "./operators/concat.js";
|
|
@@ -34,6 +35,7 @@ export {
|
|
|
34
35
|
ReduceOperator,
|
|
35
36
|
RootStreamBuilder,
|
|
36
37
|
StreamBuilder,
|
|
38
|
+
TapOperator,
|
|
37
39
|
TopKWithFractionalIndexOperator,
|
|
38
40
|
antiJoin,
|
|
39
41
|
concat,
|
|
@@ -64,6 +66,7 @@ export {
|
|
|
64
66
|
reduce,
|
|
65
67
|
rekey,
|
|
66
68
|
rightJoin,
|
|
69
|
+
tap,
|
|
67
70
|
topK,
|
|
68
71
|
topKWithFractionalIndex,
|
|
69
72
|
topKWithIndex,
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -6,6 +6,9 @@ export interface OrderByOptions<Ve> {
|
|
|
6
6
|
limit?: number;
|
|
7
7
|
offset?: number;
|
|
8
8
|
}
|
|
9
|
+
type OrderByWithFractionalIndexOptions<Ve> = OrderByOptions<Ve> & {
|
|
10
|
+
setSizeCallback?: (getSize: () => number) => void;
|
|
11
|
+
};
|
|
9
12
|
/**
|
|
10
13
|
* Orders the elements and limits the number of results, with optional offset
|
|
11
14
|
* This requires a keyed stream, and uses the `topK` operator to order all the elements.
|
|
@@ -25,7 +28,7 @@ export declare function orderBy<T extends KeyValue<unknown, unknown>, Ve = unkno
|
|
|
25
28
|
* @returns A piped operator that orders the elements and limits the number of results
|
|
26
29
|
*/
|
|
27
30
|
export declare function orderByWithIndex<T extends KeyValue<unknown, unknown>, Ve = unknown>(valueExtractor: (value: T extends KeyValue<unknown, infer V> ? V : never) => Ve, options?: OrderByOptions<Ve>): (stream: IStreamBuilder<T>) => IStreamBuilder<KeyValue<T extends KeyValue<infer K, unknown> ? K : never, [T extends KeyValue<unknown, infer V> ? V : never, number]>>;
|
|
28
|
-
export declare function orderByWithFractionalIndexBase<T extends KeyValue<unknown, unknown>, Ve = unknown>(topKFunction: typeof topKWithFractionalIndex, valueExtractor: (value: T extends KeyValue<unknown, infer V> ? V : never) => Ve, options?:
|
|
31
|
+
export declare function orderByWithFractionalIndexBase<T extends KeyValue<unknown, unknown>, Ve = unknown>(topKFunction: typeof topKWithFractionalIndex, valueExtractor: (value: T extends KeyValue<unknown, infer V> ? V : never) => Ve, options?: OrderByWithFractionalIndexOptions<Ve>): (stream: IStreamBuilder<T>) => IStreamBuilder<[T extends KeyValue<infer K, unknown> ? K : never, [T extends KeyValue<unknown, infer V> ? V : never, string]]>;
|
|
29
32
|
/**
|
|
30
33
|
* Orders the elements and limits the number of results, with optional offset and
|
|
31
34
|
* annotates the value with a fractional index.
|
|
@@ -35,4 +38,5 @@ export declare function orderByWithFractionalIndexBase<T extends KeyValue<unknow
|
|
|
35
38
|
* @param options - An optional object containing comparator, limit and offset properties
|
|
36
39
|
* @returns A piped operator that orders the elements and limits the number of results
|
|
37
40
|
*/
|
|
38
|
-
export declare function orderByWithFractionalIndex<T extends KeyValue<unknown, unknown>, Ve = unknown>(valueExtractor: (value: T extends KeyValue<unknown, infer V> ? V : never) => Ve, options?:
|
|
41
|
+
export declare function orderByWithFractionalIndex<T extends KeyValue<unknown, unknown>, Ve = unknown>(valueExtractor: (value: T extends KeyValue<unknown, infer V> ? V : never) => Ve, options?: OrderByWithFractionalIndexOptions<Ve>): (stream: IStreamBuilder<T>) => IStreamBuilder<[T extends KeyValue<infer K, unknown> ? K : never, [T extends KeyValue<unknown, infer V> ? V : never, string]]>;
|
|
42
|
+
export {};
|
|
@@ -68,6 +68,7 @@ function orderByWithIndex(valueExtractor, options) {
|
|
|
68
68
|
function orderByWithFractionalIndexBase(topKFunction, valueExtractor, options) {
|
|
69
69
|
const limit = (options == null ? void 0 : options.limit) ?? Infinity;
|
|
70
70
|
const offset = (options == null ? void 0 : options.offset) ?? 0;
|
|
71
|
+
const setSizeCallback = options == null ? void 0 : options.setSizeCallback;
|
|
71
72
|
const comparator = (options == null ? void 0 : options.comparator) ?? ((a, b) => {
|
|
72
73
|
if (a === b) return 0;
|
|
73
74
|
if (a < b) return -1;
|
|
@@ -79,7 +80,8 @@ function orderByWithFractionalIndexBase(topKFunction, valueExtractor, options) {
|
|
|
79
80
|
(a, b) => comparator(valueExtractor(a), valueExtractor(b)),
|
|
80
81
|
{
|
|
81
82
|
limit,
|
|
82
|
-
offset
|
|
83
|
+
offset,
|
|
84
|
+
setSizeCallback
|
|
83
85
|
}
|
|
84
86
|
),
|
|
85
87
|
consolidate()
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orderBy.js","sources":["../../../src/operators/orderBy.ts"],"sourcesContent":["import { topK, topKWithIndex } from \"./topK.js\"\nimport { topKWithFractionalIndex } from \"./topKWithFractionalIndex.js\"\nimport { map } from \"./map.js\"\nimport { innerJoin } from \"./join.js\"\nimport { consolidate } from \"./consolidate.js\"\nimport type { KeyValue } from \"../types.js\"\nimport type { IStreamBuilder } from \"../types\"\n\nexport interface OrderByOptions<Ve> {\n comparator?: (a: Ve, b: Ve) => number\n limit?: number\n offset?: number\n}\n\n/**\n * Orders the elements and limits the number of results, with optional offset\n * This requires a keyed stream, and uses the `topK` operator to order all the elements.\n *\n * @param valueExtractor - A function that extracts the value to order by from the element\n * @param options - An optional object containing comparator, limit and offset properties\n * @returns A piped operator that orders the elements and limits the number of results\n */\nexport function orderBy<T extends KeyValue<unknown, unknown>, Ve = unknown>(\n valueExtractor: (\n value: T extends KeyValue<unknown, infer V> ? V : never\n ) => Ve,\n options?: OrderByOptions<Ve>\n) {\n const limit = options?.limit ?? Infinity\n const offset = options?.offset ?? 0\n const comparator =\n options?.comparator ??\n ((a, b) => {\n // Default to JS like ordering\n if (a === b) return 0\n if (a < b) return -1\n return 1\n })\n\n return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {\n type KeyType = T extends KeyValue<infer K, unknown> ? K : never\n\n return stream.pipe(\n map(\n ([key, value]) =>\n [\n null,\n [\n key,\n valueExtractor(\n value as T extends KeyValue<unknown, infer V> ? V : never\n ),\n ],\n ] as KeyValue<null, [KeyType, Ve]>\n ),\n topK((a, b) => comparator(a[1], b[1]), { limit, offset }),\n map(([_, [key]]) => [key, null] as KeyValue<KeyType, null>),\n innerJoin(stream),\n map(([key, value]) => {\n return [key, value[1]] as T\n }),\n consolidate()\n )\n }\n}\n\n/**\n * Orders the elements and limits the number of results, with optional offset and\n * annotates the value with the index.\n * This requires a keyed stream, and uses the `topKWithIndex` operator to order all the elements.\n *\n * @param valueExtractor - A function that extracts the value to order by from the element\n * @param options - An optional object containing comparator, limit and offset properties\n * @returns A piped operator that orders the elements and limits the number of results\n */\nexport function orderByWithIndex<\n T extends KeyValue<unknown, unknown>,\n Ve = unknown,\n>(\n valueExtractor: (\n value: T extends KeyValue<unknown, infer V> ? V : never\n ) => Ve,\n options?: OrderByOptions<Ve>\n) {\n const limit = options?.limit ?? Infinity\n const offset = options?.offset ?? 0\n const comparator =\n options?.comparator ??\n ((a, b) => {\n // Default to JS like ordering\n if (a === b) return 0\n if (a < b) return -1\n return 1\n })\n\n return (\n stream: IStreamBuilder<T>\n ): IStreamBuilder<\n KeyValue<\n T extends KeyValue<infer K, unknown> ? K : never,\n [T extends KeyValue<unknown, infer V> ? V : never, number]\n >\n > => {\n type KeyType = T extends KeyValue<infer K, unknown> ? K : never\n type ValueType = T extends KeyValue<unknown, infer V> ? V : never\n\n return stream.pipe(\n map(\n ([key, value]) =>\n [\n null,\n [\n key,\n valueExtractor(\n value as T extends KeyValue<unknown, infer V> ? V : never\n ),\n ],\n ] as KeyValue<null, [KeyType, Ve]>\n ),\n topKWithIndex((a, b) => comparator(a[1], b[1]), { limit, offset }),\n map(([_, [[key], index]]) => [key, index] as KeyValue<KeyType, number>),\n innerJoin(stream),\n map(([key, [index, value]]) => {\n return [key, [value, index]] as KeyValue<KeyType, [ValueType, number]>\n }),\n consolidate()\n )\n }\n}\n\nexport function orderByWithFractionalIndexBase<\n T extends KeyValue<unknown, unknown>,\n Ve = unknown,\n>(\n topKFunction: typeof topKWithFractionalIndex,\n valueExtractor: (\n value: T extends KeyValue<unknown, infer V> ? V : never\n ) => Ve,\n options?:
|
|
1
|
+
{"version":3,"file":"orderBy.js","sources":["../../../src/operators/orderBy.ts"],"sourcesContent":["import { topK, topKWithIndex } from \"./topK.js\"\nimport { topKWithFractionalIndex } from \"./topKWithFractionalIndex.js\"\nimport { map } from \"./map.js\"\nimport { innerJoin } from \"./join.js\"\nimport { consolidate } from \"./consolidate.js\"\nimport type { KeyValue } from \"../types.js\"\nimport type { IStreamBuilder } from \"../types\"\n\nexport interface OrderByOptions<Ve> {\n comparator?: (a: Ve, b: Ve) => number\n limit?: number\n offset?: number\n}\n\ntype OrderByWithFractionalIndexOptions<Ve> = OrderByOptions<Ve> & {\n setSizeCallback?: (getSize: () => number) => void\n}\n\n/**\n * Orders the elements and limits the number of results, with optional offset\n * This requires a keyed stream, and uses the `topK` operator to order all the elements.\n *\n * @param valueExtractor - A function that extracts the value to order by from the element\n * @param options - An optional object containing comparator, limit and offset properties\n * @returns A piped operator that orders the elements and limits the number of results\n */\nexport function orderBy<T extends KeyValue<unknown, unknown>, Ve = unknown>(\n valueExtractor: (\n value: T extends KeyValue<unknown, infer V> ? V : never\n ) => Ve,\n options?: OrderByOptions<Ve>\n) {\n const limit = options?.limit ?? Infinity\n const offset = options?.offset ?? 0\n const comparator =\n options?.comparator ??\n ((a, b) => {\n // Default to JS like ordering\n if (a === b) return 0\n if (a < b) return -1\n return 1\n })\n\n return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {\n type KeyType = T extends KeyValue<infer K, unknown> ? K : never\n\n return stream.pipe(\n map(\n ([key, value]) =>\n [\n null,\n [\n key,\n valueExtractor(\n value as T extends KeyValue<unknown, infer V> ? V : never\n ),\n ],\n ] as KeyValue<null, [KeyType, Ve]>\n ),\n topK((a, b) => comparator(a[1], b[1]), { limit, offset }),\n map(([_, [key]]) => [key, null] as KeyValue<KeyType, null>),\n innerJoin(stream),\n map(([key, value]) => {\n return [key, value[1]] as T\n }),\n consolidate()\n )\n }\n}\n\n/**\n * Orders the elements and limits the number of results, with optional offset and\n * annotates the value with the index.\n * This requires a keyed stream, and uses the `topKWithIndex` operator to order all the elements.\n *\n * @param valueExtractor - A function that extracts the value to order by from the element\n * @param options - An optional object containing comparator, limit and offset properties\n * @returns A piped operator that orders the elements and limits the number of results\n */\nexport function orderByWithIndex<\n T extends KeyValue<unknown, unknown>,\n Ve = unknown,\n>(\n valueExtractor: (\n value: T extends KeyValue<unknown, infer V> ? V : never\n ) => Ve,\n options?: OrderByOptions<Ve>\n) {\n const limit = options?.limit ?? Infinity\n const offset = options?.offset ?? 0\n const comparator =\n options?.comparator ??\n ((a, b) => {\n // Default to JS like ordering\n if (a === b) return 0\n if (a < b) return -1\n return 1\n })\n\n return (\n stream: IStreamBuilder<T>\n ): IStreamBuilder<\n KeyValue<\n T extends KeyValue<infer K, unknown> ? K : never,\n [T extends KeyValue<unknown, infer V> ? V : never, number]\n >\n > => {\n type KeyType = T extends KeyValue<infer K, unknown> ? K : never\n type ValueType = T extends KeyValue<unknown, infer V> ? V : never\n\n return stream.pipe(\n map(\n ([key, value]) =>\n [\n null,\n [\n key,\n valueExtractor(\n value as T extends KeyValue<unknown, infer V> ? V : never\n ),\n ],\n ] as KeyValue<null, [KeyType, Ve]>\n ),\n topKWithIndex((a, b) => comparator(a[1], b[1]), { limit, offset }),\n map(([_, [[key], index]]) => [key, index] as KeyValue<KeyType, number>),\n innerJoin(stream),\n map(([key, [index, value]]) => {\n return [key, [value, index]] as KeyValue<KeyType, [ValueType, number]>\n }),\n consolidate()\n )\n }\n}\n\nexport function orderByWithFractionalIndexBase<\n T extends KeyValue<unknown, unknown>,\n Ve = unknown,\n>(\n topKFunction: typeof topKWithFractionalIndex,\n valueExtractor: (\n value: T extends KeyValue<unknown, infer V> ? V : never\n ) => Ve,\n options?: OrderByWithFractionalIndexOptions<Ve>\n) {\n type KeyType = T extends KeyValue<infer K, unknown> ? K : never\n type ValueType = T extends KeyValue<unknown, infer V> ? V : never\n\n const limit = options?.limit ?? Infinity\n const offset = options?.offset ?? 0\n const setSizeCallback = options?.setSizeCallback\n const comparator =\n options?.comparator ??\n ((a, b) => {\n // Default to JS like ordering\n if (a === b) return 0\n if (a < b) return -1\n return 1\n })\n\n return (\n stream: IStreamBuilder<T>\n ): IStreamBuilder<[KeyType, [ValueType, string]]> => {\n return stream.pipe(\n topKFunction(\n (a: ValueType, b: ValueType) =>\n comparator(valueExtractor(a), valueExtractor(b)),\n {\n limit,\n offset,\n setSizeCallback,\n }\n ),\n consolidate()\n )\n }\n}\n\n/**\n * Orders the elements and limits the number of results, with optional offset and\n * annotates the value with a fractional index.\n * This requires a keyed stream, and uses the `topKWithFractionalIndex` operator to order all the elements.\n *\n * @param valueExtractor - A function that extracts the value to order by from the element\n * @param options - An optional object containing comparator, limit and offset properties\n * @returns A piped operator that orders the elements and limits the number of results\n */\nexport function orderByWithFractionalIndex<\n T extends KeyValue<unknown, unknown>,\n Ve = unknown,\n>(\n valueExtractor: (\n value: T extends KeyValue<unknown, infer V> ? V : never\n ) => Ve,\n options?: OrderByWithFractionalIndexOptions<Ve>\n) {\n return orderByWithFractionalIndexBase(\n topKWithFractionalIndex,\n valueExtractor,\n options\n )\n}\n"],"names":[],"mappings":";;;;;AA0BO,SAAS,QACd,gBAGA,SACA;AACA,QAAM,SAAQ,mCAAS,UAAS;AAChC,QAAM,UAAS,mCAAS,WAAU;AAClC,QAAM,cACJ,mCAAS,gBACR,CAAC,GAAG,MAAM;AAET,QAAI,MAAM,EAAG,QAAO;AACpB,QAAI,IAAI,EAAG,QAAO;AAClB,WAAO;AAAA,EACT;AAEF,SAAO,CAAC,WAAiD;AAGvD,WAAO,OAAO;AAAA,MACZ;AAAA,QACE,CAAC,CAAC,KAAK,KAAK,MACV;AAAA,UACE;AAAA,UACA;AAAA,YACE;AAAA,YACA;AAAA,cACE;AAAA,YAAA;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,MAEJ,KAAK,CAAC,GAAG,MAAM,WAAW,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,OAAO,QAAQ;AAAA,MACxD,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,CAA4B;AAAA,MAC1D,UAAU,MAAM;AAAA,MAChB,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM;AACpB,eAAO,CAAC,KAAK,MAAM,CAAC,CAAC;AAAA,MACvB,CAAC;AAAA,MACD,YAAA;AAAA,IAAY;AAAA,EAEhB;AACF;AAWO,SAAS,iBAId,gBAGA,SACA;AACA,QAAM,SAAQ,mCAAS,UAAS;AAChC,QAAM,UAAS,mCAAS,WAAU;AAClC,QAAM,cACJ,mCAAS,gBACR,CAAC,GAAG,MAAM;AAET,QAAI,MAAM,EAAG,QAAO;AACpB,QAAI,IAAI,EAAG,QAAO;AAClB,WAAO;AAAA,EACT;AAEF,SAAO,CACL,WAMG;AAIH,WAAO,OAAO;AAAA,MACZ;AAAA,QACE,CAAC,CAAC,KAAK,KAAK,MACV;AAAA,UACE;AAAA,UACA;AAAA,YACE;AAAA,YACA;AAAA,cACE;AAAA,YAAA;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,MAEJ,cAAc,CAAC,GAAG,MAAM,WAAW,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,OAAO,QAAQ;AAAA,MACjE,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,KAAK,CAA8B;AAAA,MACtE,UAAU,MAAM;AAAA,MAChB,IAAI,CAAC,CAAC,KAAK,CAAC,OAAO,KAAK,CAAC,MAAM;AAC7B,eAAO,CAAC,KAAK,CAAC,OAAO,KAAK,CAAC;AAAA,MAC7B,CAAC;AAAA,MACD,YAAA;AAAA,IAAY;AAAA,EAEhB;AACF;AAEO,SAAS,+BAId,cACA,gBAGA,SACA;AAIA,QAAM,SAAQ,mCAAS,UAAS;AAChC,QAAM,UAAS,mCAAS,WAAU;AAClC,QAAM,kBAAkB,mCAAS;AACjC,QAAM,cACJ,mCAAS,gBACR,CAAC,GAAG,MAAM;AAET,QAAI,MAAM,EAAG,QAAO;AACpB,QAAI,IAAI,EAAG,QAAO;AAClB,WAAO;AAAA,EACT;AAEF,SAAO,CACL,WACmD;AACnD,WAAO,OAAO;AAAA,MACZ;AAAA,QACE,CAAC,GAAc,MACb,WAAW,eAAe,CAAC,GAAG,eAAe,CAAC,CAAC;AAAA,QACjD;AAAA,UACE;AAAA,UACA;AAAA,UACA;AAAA,QAAA;AAAA,MACF;AAAA,MAEF,YAAA;AAAA,IAAY;AAAA,EAEhB;AACF;AAWO,SAAS,2BAId,gBAGA,SACA;AACA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { DifferenceStreamWriter, LinearUnaryOperator, DifferenceStreamReader } from '../graph.js';
|
|
2
|
+
import { PipedOperator } from '../types.js';
|
|
3
|
+
import { MultiSet } from '../multiset.js';
|
|
4
|
+
/**
|
|
5
|
+
* Operator that applies a function to each element in the input stream
|
|
6
|
+
*/
|
|
7
|
+
export declare class TapOperator<T> extends LinearUnaryOperator<T, T> {
|
|
8
|
+
#private;
|
|
9
|
+
constructor(id: number, inputA: DifferenceStreamReader<T>, output: DifferenceStreamWriter<T>, f: (data: T) => void);
|
|
10
|
+
inner(collection: MultiSet<T>): MultiSet<T>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Invokes a function for each element in the input stream.
|
|
14
|
+
* This operator doesn't modify the stream and is used to perform side effects.
|
|
15
|
+
* @param f - The function to invoke on each element
|
|
16
|
+
* @returns The input stream
|
|
17
|
+
*/
|
|
18
|
+
export declare function tap<T>(f: (data: T) => void): PipedOperator<T, T>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
var __typeError = (msg) => {
|
|
2
|
+
throw TypeError(msg);
|
|
3
|
+
};
|
|
4
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
5
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
6
|
+
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
7
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
8
|
+
var _f;
|
|
9
|
+
import { LinearUnaryOperator, DifferenceStreamWriter } from "../graph.js";
|
|
10
|
+
import { StreamBuilder } from "../d2.js";
|
|
11
|
+
class TapOperator extends LinearUnaryOperator {
|
|
12
|
+
constructor(id, inputA, output, f) {
|
|
13
|
+
super(id, inputA, output);
|
|
14
|
+
__privateAdd(this, _f);
|
|
15
|
+
__privateSet(this, _f, f);
|
|
16
|
+
}
|
|
17
|
+
inner(collection) {
|
|
18
|
+
return collection.map((data) => {
|
|
19
|
+
__privateGet(this, _f).call(this, data);
|
|
20
|
+
return data;
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
_f = new WeakMap();
|
|
25
|
+
function tap(f) {
|
|
26
|
+
return (stream) => {
|
|
27
|
+
const output = new StreamBuilder(
|
|
28
|
+
stream.graph,
|
|
29
|
+
new DifferenceStreamWriter()
|
|
30
|
+
);
|
|
31
|
+
const operator = new TapOperator(
|
|
32
|
+
stream.graph.getNextOperatorId(),
|
|
33
|
+
stream.connectReader(),
|
|
34
|
+
output.writer,
|
|
35
|
+
f
|
|
36
|
+
);
|
|
37
|
+
stream.graph.addOperator(operator);
|
|
38
|
+
stream.graph.addStream(output.connectReader());
|
|
39
|
+
return output;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
TapOperator,
|
|
44
|
+
tap
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=tap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tap.js","sources":["../../../src/operators/tap.ts"],"sourcesContent":["import { DifferenceStreamWriter, LinearUnaryOperator } from \"../graph.js\"\nimport { StreamBuilder } from \"../d2.js\"\nimport type { IStreamBuilder, PipedOperator } from \"../types.js\"\nimport type { DifferenceStreamReader } from \"../graph.js\"\nimport type { MultiSet } from \"../multiset.js\"\n\n/**\n * Operator that applies a function to each element in the input stream\n */\nexport class TapOperator<T> extends LinearUnaryOperator<T, T> {\n #f: (data: T) => void\n\n constructor(\n id: number,\n inputA: DifferenceStreamReader<T>,\n output: DifferenceStreamWriter<T>,\n f: (data: T) => void\n ) {\n super(id, inputA, output)\n this.#f = f\n }\n\n inner(collection: MultiSet<T>): MultiSet<T> {\n return collection.map((data) => {\n this.#f(data)\n return data\n })\n }\n}\n\n/**\n * Invokes a function for each element in the input stream.\n * This operator doesn't modify the stream and is used to perform side effects.\n * @param f - The function to invoke on each element\n * @returns The input stream\n */\nexport function tap<T>(f: (data: T) => void): PipedOperator<T, T> {\n return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {\n const output = new StreamBuilder<T>(\n stream.graph,\n new DifferenceStreamWriter<T>()\n )\n const operator = new TapOperator<T>(\n stream.graph.getNextOperatorId(),\n stream.connectReader(),\n output.writer,\n f\n )\n stream.graph.addOperator(operator)\n stream.graph.addStream(output.connectReader())\n return output\n }\n}\n"],"names":[],"mappings":";;;;;;;;;;AASO,MAAM,oBAAuB,oBAA0B;AAAA,EAG5D,YACE,IACA,QACA,QACA,GACA;AACA,UAAM,IAAI,QAAQ,MAAM;AAR1B;AASE,uBAAK,IAAK;AAAA,EACZ;AAAA,EAEA,MAAM,YAAsC;AAC1C,WAAO,WAAW,IAAI,CAAC,SAAS;AAC9B,yBAAK,IAAL,WAAQ;AACR,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AACF;AAlBE;AA0BK,SAAS,IAAO,GAA2C;AAChE,SAAO,CAAC,WAAiD;AACvD,UAAM,SAAS,IAAI;AAAA,MACjB,OAAO;AAAA,MACP,IAAI,uBAAA;AAAA,IAA0B;AAEhC,UAAM,WAAW,IAAI;AAAA,MACnB,OAAO,MAAM,kBAAA;AAAA,MACb,OAAO,cAAA;AAAA,MACP,OAAO;AAAA,MACP;AAAA,IAAA;AAEF,WAAO,MAAM,YAAY,QAAQ;AACjC,WAAO,MAAM,UAAU,OAAO,cAAA,CAAe;AAC7C,WAAO;AAAA,EACT;AACF;"}
|
|
@@ -3,6 +3,7 @@ import { PipedOperator } from '../types.js';
|
|
|
3
3
|
export interface TopKWithFractionalIndexOptions {
|
|
4
4
|
limit?: number;
|
|
5
5
|
offset?: number;
|
|
6
|
+
setSizeCallback?: (getSize: () => number) => void;
|
|
6
7
|
}
|
|
7
8
|
export type TopKChanges<V> = {
|
|
8
9
|
/** Indicates which element moves into the topK (if any) */
|
|
@@ -15,6 +16,7 @@ export type TopKChanges<V> = {
|
|
|
15
16
|
* and returns changes to the topK.
|
|
16
17
|
*/
|
|
17
18
|
export interface TopK<V> {
|
|
19
|
+
size: number;
|
|
18
20
|
insert: (value: V) => TopKChanges<V>;
|
|
19
21
|
delete: (value: V) => TopKChanges<V>;
|
|
20
22
|
}
|
|
@@ -6,7 +6,7 @@ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read fr
|
|
|
6
6
|
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
7
7
|
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
8
8
|
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
9
|
-
var _sortedValues, _comparator, _topKStart, _topKEnd, _TopKArray_instances, findIndex_fn, _index, _topK;
|
|
9
|
+
var _sortedValues, _comparator, _topKStart, _topKEnd, _TopKArray_instances, findIndex_fn, _index, _topK, _limit;
|
|
10
10
|
import { generateKeyBetween } from "fractional-indexing";
|
|
11
11
|
import { UnaryOperator, DifferenceStreamWriter } from "../graph.js";
|
|
12
12
|
import { StreamBuilder } from "../d2.js";
|
|
@@ -23,6 +23,12 @@ class TopKArray {
|
|
|
23
23
|
__privateSet(this, _topKEnd, offset + limit);
|
|
24
24
|
__privateSet(this, _comparator, comparator);
|
|
25
25
|
}
|
|
26
|
+
get size() {
|
|
27
|
+
const offset = __privateGet(this, _topKStart);
|
|
28
|
+
const limit = __privateGet(this, _topKEnd) - __privateGet(this, _topKStart);
|
|
29
|
+
const available = __privateGet(this, _sortedValues).length - offset;
|
|
30
|
+
return Math.max(0, Math.min(limit, available));
|
|
31
|
+
}
|
|
26
32
|
insert(value) {
|
|
27
33
|
const result = { moveIn: null, moveOut: null };
|
|
28
34
|
const index = __privateMethod(this, _TopKArray_instances, findIndex_fn).call(this, value);
|
|
@@ -87,6 +93,7 @@ findIndex_fn = function(value) {
|
|
|
87
93
|
};
|
|
88
94
|
class TopKWithFractionalIndexOperator extends UnaryOperator {
|
|
89
95
|
constructor(id, inputA, output, comparator, options) {
|
|
96
|
+
var _a;
|
|
90
97
|
super(id, inputA, output);
|
|
91
98
|
__privateAdd(this, _index, /* @__PURE__ */ new Map());
|
|
92
99
|
// maps keys to their multiplicity
|
|
@@ -95,7 +102,8 @@ class TopKWithFractionalIndexOperator extends UnaryOperator {
|
|
|
95
102
|
* and returns changes to the topK.
|
|
96
103
|
*/
|
|
97
104
|
__privateAdd(this, _topK);
|
|
98
|
-
|
|
105
|
+
__privateAdd(this, _limit);
|
|
106
|
+
__privateSet(this, _limit, options.limit ?? Infinity);
|
|
99
107
|
const offset = options.offset ?? 0;
|
|
100
108
|
const compareTaggedValues = (a, b) => {
|
|
101
109
|
const valueComparison = comparator(getVal(a), getVal(b));
|
|
@@ -106,7 +114,8 @@ class TopKWithFractionalIndexOperator extends UnaryOperator {
|
|
|
106
114
|
const tieBreakerB = getTag(b);
|
|
107
115
|
return tieBreakerA - tieBreakerB;
|
|
108
116
|
};
|
|
109
|
-
__privateSet(this, _topK, this.createTopK(offset,
|
|
117
|
+
__privateSet(this, _topK, this.createTopK(offset, __privateGet(this, _limit), compareTaggedValues));
|
|
118
|
+
(_a = options.setSizeCallback) == null ? void 0 : _a.call(options, () => __privateGet(this, _topK).size);
|
|
110
119
|
}
|
|
111
120
|
createTopK(offset, limit, comparator) {
|
|
112
121
|
return new TopKArray(offset, limit, comparator);
|
|
@@ -168,6 +177,7 @@ class TopKWithFractionalIndexOperator extends UnaryOperator {
|
|
|
168
177
|
}
|
|
169
178
|
_index = new WeakMap();
|
|
170
179
|
_topK = new WeakMap();
|
|
180
|
+
_limit = new WeakMap();
|
|
171
181
|
function topKWithFractionalIndex(comparator, options) {
|
|
172
182
|
const opts = options || {};
|
|
173
183
|
return (stream) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"topKWithFractionalIndex.js","sources":["../../../src/operators/topKWithFractionalIndex.ts"],"sourcesContent":["import { generateKeyBetween } from \"fractional-indexing\"\nimport { DifferenceStreamWriter, UnaryOperator } from \"../graph.js\"\nimport { StreamBuilder } from \"../d2.js\"\nimport { MultiSet } from \"../multiset.js\"\nimport { binarySearch, globalObjectIdGenerator } from \"../utils.js\"\nimport type { DifferenceStreamReader } from \"../graph.js\"\nimport type { IStreamBuilder, PipedOperator } from \"../types.js\"\n\nexport interface TopKWithFractionalIndexOptions {\n limit?: number\n offset?: number\n}\n\nexport type TopKChanges<V> = {\n /** Indicates which element moves into the topK (if any) */\n moveIn: IndexedValue<V> | null\n /** Indicates which element moves out of the topK (if any) */\n moveOut: IndexedValue<V> | null\n}\n\n/**\n * A topK data structure that supports insertions and deletions\n * and returns changes to the topK.\n */\nexport interface TopK<V> {\n insert: (value: V) => TopKChanges<V>\n delete: (value: V) => TopKChanges<V>\n}\n\n/**\n * Implementation of a topK data structure.\n * Uses a sorted array internally to store the values and keeps a topK window over that array.\n * Inserts and deletes are O(n) operations because worst case an element is inserted/deleted\n * at the start of the array which causes all the elements to shift to the right/left.\n */\nclass TopKArray<V> implements TopK<V> {\n #sortedValues: Array<IndexedValue<V>> = []\n #comparator: (a: V, b: V) => number\n #topKStart: number\n #topKEnd: number\n\n constructor(\n offset: number,\n limit: number,\n comparator: (a: V, b: V) => number\n ) {\n this.#topKStart = offset\n this.#topKEnd = offset + limit\n this.#comparator = comparator\n }\n\n insert(value: V): TopKChanges<V> {\n const result: TopKChanges<V> = { moveIn: null, moveOut: null }\n\n // Lookup insert position\n const index = this.#findIndex(value)\n // Generate fractional index based on the fractional indices of the elements before and after it\n const indexBefore =\n index === 0 ? null : getIndex(this.#sortedValues[index - 1]!)\n const indexAfter =\n index === this.#sortedValues.length\n ? null\n : getIndex(this.#sortedValues[index]!)\n const fractionalIndex = generateKeyBetween(indexBefore, indexAfter)\n\n // Insert the value at the correct position\n const val = indexedValue(value, fractionalIndex)\n // Splice is O(n) where n = all elements in the collection (i.e. n >= k) !\n this.#sortedValues.splice(index, 0, val)\n\n // Check if the topK changed\n if (index < this.#topKEnd) {\n // The inserted element is either before the top K or within the top K\n // If it is before the top K then it moves the element that was right before the topK into the topK\n // If it is within the top K then the inserted element moves into the top K\n // In both cases the last element of the old top K now moves out of the top K\n const moveInIndex = Math.max(index, this.#topKStart)\n if (moveInIndex < this.#sortedValues.length) {\n // We actually have a topK\n // because in some cases there may not be enough elements in the array to reach the start of the topK\n // e.g. [1, 2, 3] with K = 2 and offset = 3 does not have a topK\n result.moveIn = this.#sortedValues[moveInIndex]!\n\n // We need to remove the element that falls out of the top K\n // The element that falls out of the top K has shifted one to the right\n // because of the element we inserted, so we find it at index topKEnd\n if (this.#topKEnd < this.#sortedValues.length) {\n result.moveOut = this.#sortedValues[this.#topKEnd]!\n }\n }\n }\n\n return result\n }\n\n /**\n * Deletes a value that may or may not be in the topK.\n * IMPORTANT: this assumes that the value is present in the collection\n * if it's not the case it will remove the element\n * that is on the position where the provided `value` would be.\n */\n delete(value: V): TopKChanges<V> {\n const result: TopKChanges<V> = { moveIn: null, moveOut: null }\n\n // Lookup delete position\n const index = this.#findIndex(value)\n // Remove the value at that position\n const [removedElem] = this.#sortedValues.splice(index, 1)\n\n // Check if the topK changed\n if (index < this.#topKEnd) {\n // The removed element is either before the top K or within the top K\n // If it is before the top K then the first element of the topK moves out of the topK\n // If it is within the top K then the removed element moves out of the topK\n result.moveOut = removedElem!\n if (index < this.#topKStart) {\n // The removed element is before the topK\n // so actually, the first element of the topK moves out of the topK\n // and not the element that we removed\n // The first element of the topK is now at index topKStart - 1\n // since we removed an element before the topK\n const moveOutIndex = this.#topKStart - 1\n if (moveOutIndex < this.#sortedValues.length) {\n result.moveOut = this.#sortedValues[moveOutIndex]!\n } else {\n // No value is moving out of the topK\n // because there are no elements in the topK\n result.moveOut = null\n }\n }\n\n // Since we removed an element that was before or in the topK\n // the first element after the topK moved one position to the left\n // and thus falls into the topK now\n const moveInIndex = this.#topKEnd - 1\n if (moveInIndex < this.#sortedValues.length) {\n result.moveIn = this.#sortedValues[moveInIndex]!\n }\n }\n\n return result\n }\n\n // TODO: see if there is a way to refactor the code for insert and delete in the topK above\n // because they are very similar, one is shifting the topK window to the left and the other is shifting it to the right\n // so i have the feeling there is a common pattern here and we can implement both cases using that pattern\n\n #findIndex(value: V): number {\n return binarySearch(this.#sortedValues, indexedValue(value, ``), (a, b) =>\n this.#comparator(getValue(a), getValue(b))\n )\n }\n}\n\n/**\n * Operator for fractional indexed topK operations\n * This operator maintains fractional indices for sorted elements\n * and only updates indices when elements move position\n */\nexport class TopKWithFractionalIndexOperator<K, T> extends UnaryOperator<\n [K, T],\n [K, IndexedValue<T>]\n> {\n #index: Map<K, number> = new Map() // maps keys to their multiplicity\n\n /**\n * topK data structure that supports insertions and deletions\n * and returns changes to the topK.\n */\n #topK: TopK<TaggedValue<K, T>>\n\n constructor(\n id: number,\n inputA: DifferenceStreamReader<[K, T]>,\n output: DifferenceStreamWriter<[K, IndexedValue<T>]>,\n comparator: (a: T, b: T) => number,\n options: TopKWithFractionalIndexOptions\n ) {\n super(id, inputA, output)\n const limit = options.limit ?? Infinity\n const offset = options.offset ?? 0\n const compareTaggedValues = (\n a: TaggedValue<K, T>,\n b: TaggedValue<K, T>\n ) => {\n // First compare on the value\n const valueComparison = comparator(getVal(a), getVal(b))\n if (valueComparison !== 0) {\n return valueComparison\n }\n // If the values are equal, compare on the tag (object identity)\n const tieBreakerA = getTag(a)\n const tieBreakerB = getTag(b)\n return tieBreakerA - tieBreakerB\n }\n this.#topK = this.createTopK(offset, limit, compareTaggedValues)\n }\n\n protected createTopK(\n offset: number,\n limit: number,\n comparator: (a: TaggedValue<K, T>, b: TaggedValue<K, T>) => number\n ): TopK<TaggedValue<K, T>> {\n return new TopKArray(offset, limit, comparator)\n }\n\n run(): void {\n const result: Array<[[K, IndexedValue<T>], number]> = []\n for (const message of this.inputMessages()) {\n for (const [item, multiplicity] of message.getInner()) {\n const [key, value] = item\n this.processElement(key, value, multiplicity, result)\n }\n }\n\n if (result.length > 0) {\n this.output.sendData(new MultiSet(result))\n }\n }\n\n processElement(\n key: K,\n value: T,\n multiplicity: number,\n result: Array<[[K, IndexedValue<T>], number]>\n ): void {\n const { oldMultiplicity, newMultiplicity } = this.addKey(key, multiplicity)\n\n let res: TopKChanges<TaggedValue<K, T>> = {\n moveIn: null,\n moveOut: null,\n }\n if (oldMultiplicity <= 0 && newMultiplicity > 0) {\n // The value was invisible but should now be visible\n // Need to insert it into the array of sorted values\n const taggedValue = tagValue(key, value)\n res = this.#topK.insert(taggedValue)\n } else if (oldMultiplicity > 0 && newMultiplicity <= 0) {\n // The value was visible but should now be invisible\n // Need to remove it from the array of sorted values\n const taggedValue = tagValue(key, value)\n res = this.#topK.delete(taggedValue)\n } else {\n // The value was invisible and it remains invisible\n // or it was visible and remains visible\n // so it doesn't affect the topK\n }\n\n if (res.moveIn) {\n const index = getIndex(res.moveIn)\n const taggedValue = getValue(res.moveIn)\n const k = getKey(taggedValue)\n const val = getVal(taggedValue)\n result.push([[k, [val, index]], 1])\n }\n\n if (res.moveOut) {\n const index = getIndex(res.moveOut)\n const taggedValue = getValue(res.moveOut)\n const k = getKey(taggedValue)\n const val = getVal(taggedValue)\n result.push([[k, [val, index]], -1])\n }\n\n return\n }\n\n private getMultiplicity(key: K): number {\n return this.#index.get(key) ?? 0\n }\n\n private addKey(\n key: K,\n multiplicity: number\n ): { oldMultiplicity: number; newMultiplicity: number } {\n const oldMultiplicity = this.getMultiplicity(key)\n const newMultiplicity = oldMultiplicity + multiplicity\n if (newMultiplicity === 0) {\n this.#index.delete(key)\n } else {\n this.#index.set(key, newMultiplicity)\n }\n return { oldMultiplicity, newMultiplicity }\n }\n}\n\n/**\n * Limits the number of results based on a comparator, with optional offset.\n * Uses fractional indexing to minimize the number of changes when elements move positions.\n * Each element is assigned a fractional index that is lexicographically sortable.\n * When elements move, only the indices of the moved elements are updated, not all elements.\n *\n * @param comparator - A function that compares two elements\n * @param options - An optional object containing limit and offset properties\n * @returns A piped operator that orders the elements and limits the number of results\n */\nexport function topKWithFractionalIndex<KType, T>(\n comparator: (a: T, b: T) => number,\n options?: TopKWithFractionalIndexOptions\n): PipedOperator<[KType, T], [KType, IndexedValue<T>]> {\n const opts = options || {}\n\n return (\n stream: IStreamBuilder<[KType, T]>\n ): IStreamBuilder<[KType, IndexedValue<T>]> => {\n const output = new StreamBuilder<[KType, IndexedValue<T>]>(\n stream.graph,\n new DifferenceStreamWriter<[KType, IndexedValue<T>]>()\n )\n const operator = new TopKWithFractionalIndexOperator<KType, T>(\n stream.graph.getNextOperatorId(),\n stream.connectReader(),\n output.writer,\n comparator,\n opts\n )\n stream.graph.addOperator(operator)\n stream.graph.addStream(output.connectReader())\n return output\n }\n}\n\n// Abstraction for fractionally indexed values\nexport type FractionalIndex = string\nexport type IndexedValue<V> = [V, FractionalIndex]\n\nexport function indexedValue<V>(\n value: V,\n index: FractionalIndex\n): IndexedValue<V> {\n return [value, index]\n}\n\nexport function getValue<V>(indexedVal: IndexedValue<V>): V {\n return indexedVal[0]\n}\n\nexport function getIndex<V>(indexedVal: IndexedValue<V>): FractionalIndex {\n return indexedVal[1]\n}\n\nexport type Tag = number\nexport type TaggedValue<K, V> = [K, V, Tag]\n\nfunction tagValue<K, V>(key: K, value: V): TaggedValue<K, V> {\n return [key, value, globalObjectIdGenerator.getId(key)]\n}\n\nfunction getKey<K, V>(tieBreakerTaggedValue: TaggedValue<K, V>): K {\n return tieBreakerTaggedValue[0]\n}\n\nfunction getVal<K, V>(tieBreakerTaggedValue: TaggedValue<K, V>): V {\n return tieBreakerTaggedValue[1]\n}\n\nfunction getTag<K, V>(tieBreakerTaggedValue: TaggedValue<K, V>): Tag {\n return tieBreakerTaggedValue[2]\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"topKWithFractionalIndex.js","sources":["../../../src/operators/topKWithFractionalIndex.ts"],"sourcesContent":["import { generateKeyBetween } from \"fractional-indexing\"\nimport { DifferenceStreamWriter, UnaryOperator } from \"../graph.js\"\nimport { StreamBuilder } from \"../d2.js\"\nimport { MultiSet } from \"../multiset.js\"\nimport { binarySearch, globalObjectIdGenerator } from \"../utils.js\"\nimport type { DifferenceStreamReader } from \"../graph.js\"\nimport type { IStreamBuilder, PipedOperator } from \"../types.js\"\n\nexport interface TopKWithFractionalIndexOptions {\n limit?: number\n offset?: number\n setSizeCallback?: (getSize: () => number) => void\n}\n\nexport type TopKChanges<V> = {\n /** Indicates which element moves into the topK (if any) */\n moveIn: IndexedValue<V> | null\n /** Indicates which element moves out of the topK (if any) */\n moveOut: IndexedValue<V> | null\n}\n\n/**\n * A topK data structure that supports insertions and deletions\n * and returns changes to the topK.\n */\nexport interface TopK<V> {\n size: number\n insert: (value: V) => TopKChanges<V>\n delete: (value: V) => TopKChanges<V>\n}\n\n/**\n * Implementation of a topK data structure.\n * Uses a sorted array internally to store the values and keeps a topK window over that array.\n * Inserts and deletes are O(n) operations because worst case an element is inserted/deleted\n * at the start of the array which causes all the elements to shift to the right/left.\n */\nclass TopKArray<V> implements TopK<V> {\n #sortedValues: Array<IndexedValue<V>> = []\n #comparator: (a: V, b: V) => number\n #topKStart: number\n #topKEnd: number\n\n constructor(\n offset: number,\n limit: number,\n comparator: (a: V, b: V) => number\n ) {\n this.#topKStart = offset\n this.#topKEnd = offset + limit\n this.#comparator = comparator\n }\n\n get size(): number {\n const offset = this.#topKStart\n const limit = this.#topKEnd - this.#topKStart\n const available = this.#sortedValues.length - offset\n return Math.max(0, Math.min(limit, available))\n }\n\n insert(value: V): TopKChanges<V> {\n const result: TopKChanges<V> = { moveIn: null, moveOut: null }\n\n // Lookup insert position\n const index = this.#findIndex(value)\n // Generate fractional index based on the fractional indices of the elements before and after it\n const indexBefore =\n index === 0 ? null : getIndex(this.#sortedValues[index - 1]!)\n const indexAfter =\n index === this.#sortedValues.length\n ? null\n : getIndex(this.#sortedValues[index]!)\n const fractionalIndex = generateKeyBetween(indexBefore, indexAfter)\n\n // Insert the value at the correct position\n const val = indexedValue(value, fractionalIndex)\n // Splice is O(n) where n = all elements in the collection (i.e. n >= k) !\n this.#sortedValues.splice(index, 0, val)\n\n // Check if the topK changed\n if (index < this.#topKEnd) {\n // The inserted element is either before the top K or within the top K\n // If it is before the top K then it moves the element that was right before the topK into the topK\n // If it is within the top K then the inserted element moves into the top K\n // In both cases the last element of the old top K now moves out of the top K\n const moveInIndex = Math.max(index, this.#topKStart)\n if (moveInIndex < this.#sortedValues.length) {\n // We actually have a topK\n // because in some cases there may not be enough elements in the array to reach the start of the topK\n // e.g. [1, 2, 3] with K = 2 and offset = 3 does not have a topK\n result.moveIn = this.#sortedValues[moveInIndex]!\n\n // We need to remove the element that falls out of the top K\n // The element that falls out of the top K has shifted one to the right\n // because of the element we inserted, so we find it at index topKEnd\n if (this.#topKEnd < this.#sortedValues.length) {\n result.moveOut = this.#sortedValues[this.#topKEnd]!\n }\n }\n }\n\n return result\n }\n\n /**\n * Deletes a value that may or may not be in the topK.\n * IMPORTANT: this assumes that the value is present in the collection\n * if it's not the case it will remove the element\n * that is on the position where the provided `value` would be.\n */\n delete(value: V): TopKChanges<V> {\n const result: TopKChanges<V> = { moveIn: null, moveOut: null }\n\n // Lookup delete position\n const index = this.#findIndex(value)\n // Remove the value at that position\n const [removedElem] = this.#sortedValues.splice(index, 1)\n\n // Check if the topK changed\n if (index < this.#topKEnd) {\n // The removed element is either before the top K or within the top K\n // If it is before the top K then the first element of the topK moves out of the topK\n // If it is within the top K then the removed element moves out of the topK\n result.moveOut = removedElem!\n if (index < this.#topKStart) {\n // The removed element is before the topK\n // so actually, the first element of the topK moves out of the topK\n // and not the element that we removed\n // The first element of the topK is now at index topKStart - 1\n // since we removed an element before the topK\n const moveOutIndex = this.#topKStart - 1\n if (moveOutIndex < this.#sortedValues.length) {\n result.moveOut = this.#sortedValues[moveOutIndex]!\n } else {\n // No value is moving out of the topK\n // because there are no elements in the topK\n result.moveOut = null\n }\n }\n\n // Since we removed an element that was before or in the topK\n // the first element after the topK moved one position to the left\n // and thus falls into the topK now\n const moveInIndex = this.#topKEnd - 1\n if (moveInIndex < this.#sortedValues.length) {\n result.moveIn = this.#sortedValues[moveInIndex]!\n }\n }\n\n return result\n }\n\n // TODO: see if there is a way to refactor the code for insert and delete in the topK above\n // because they are very similar, one is shifting the topK window to the left and the other is shifting it to the right\n // so i have the feeling there is a common pattern here and we can implement both cases using that pattern\n\n #findIndex(value: V): number {\n return binarySearch(this.#sortedValues, indexedValue(value, ``), (a, b) =>\n this.#comparator(getValue(a), getValue(b))\n )\n }\n}\n\n/**\n * Operator for fractional indexed topK operations\n * This operator maintains fractional indices for sorted elements\n * and only updates indices when elements move position\n */\nexport class TopKWithFractionalIndexOperator<K, T> extends UnaryOperator<\n [K, T],\n [K, IndexedValue<T>]\n> {\n #index: Map<K, number> = new Map() // maps keys to their multiplicity\n\n /**\n * topK data structure that supports insertions and deletions\n * and returns changes to the topK.\n */\n #topK: TopK<TaggedValue<K, T>>\n\n #limit: number\n\n constructor(\n id: number,\n inputA: DifferenceStreamReader<[K, T]>,\n output: DifferenceStreamWriter<[K, IndexedValue<T>]>,\n comparator: (a: T, b: T) => number,\n options: TopKWithFractionalIndexOptions\n ) {\n super(id, inputA, output)\n this.#limit = options.limit ?? Infinity\n const offset = options.offset ?? 0\n const compareTaggedValues = (\n a: TaggedValue<K, T>,\n b: TaggedValue<K, T>\n ) => {\n // First compare on the value\n const valueComparison = comparator(getVal(a), getVal(b))\n if (valueComparison !== 0) {\n return valueComparison\n }\n // If the values are equal, compare on the tag (object identity)\n const tieBreakerA = getTag(a)\n const tieBreakerB = getTag(b)\n return tieBreakerA - tieBreakerB\n }\n this.#topK = this.createTopK(offset, this.#limit, compareTaggedValues)\n options.setSizeCallback?.(() => this.#topK.size)\n }\n\n protected createTopK(\n offset: number,\n limit: number,\n comparator: (a: TaggedValue<K, T>, b: TaggedValue<K, T>) => number\n ): TopK<TaggedValue<K, T>> {\n return new TopKArray(offset, limit, comparator)\n }\n\n run(): void {\n const result: Array<[[K, IndexedValue<T>], number]> = []\n for (const message of this.inputMessages()) {\n for (const [item, multiplicity] of message.getInner()) {\n const [key, value] = item\n this.processElement(key, value, multiplicity, result)\n }\n }\n\n if (result.length > 0) {\n this.output.sendData(new MultiSet(result))\n }\n }\n\n processElement(\n key: K,\n value: T,\n multiplicity: number,\n result: Array<[[K, IndexedValue<T>], number]>\n ): void {\n const { oldMultiplicity, newMultiplicity } = this.addKey(key, multiplicity)\n\n let res: TopKChanges<TaggedValue<K, T>> = {\n moveIn: null,\n moveOut: null,\n }\n if (oldMultiplicity <= 0 && newMultiplicity > 0) {\n // The value was invisible but should now be visible\n // Need to insert it into the array of sorted values\n const taggedValue = tagValue(key, value)\n res = this.#topK.insert(taggedValue)\n } else if (oldMultiplicity > 0 && newMultiplicity <= 0) {\n // The value was visible but should now be invisible\n // Need to remove it from the array of sorted values\n const taggedValue = tagValue(key, value)\n res = this.#topK.delete(taggedValue)\n } else {\n // The value was invisible and it remains invisible\n // or it was visible and remains visible\n // so it doesn't affect the topK\n }\n\n if (res.moveIn) {\n const index = getIndex(res.moveIn)\n const taggedValue = getValue(res.moveIn)\n const k = getKey(taggedValue)\n const val = getVal(taggedValue)\n result.push([[k, [val, index]], 1])\n }\n\n if (res.moveOut) {\n const index = getIndex(res.moveOut)\n const taggedValue = getValue(res.moveOut)\n const k = getKey(taggedValue)\n const val = getVal(taggedValue)\n result.push([[k, [val, index]], -1])\n }\n\n return\n }\n\n private getMultiplicity(key: K): number {\n return this.#index.get(key) ?? 0\n }\n\n private addKey(\n key: K,\n multiplicity: number\n ): { oldMultiplicity: number; newMultiplicity: number } {\n const oldMultiplicity = this.getMultiplicity(key)\n const newMultiplicity = oldMultiplicity + multiplicity\n if (newMultiplicity === 0) {\n this.#index.delete(key)\n } else {\n this.#index.set(key, newMultiplicity)\n }\n return { oldMultiplicity, newMultiplicity }\n }\n}\n\n/**\n * Limits the number of results based on a comparator, with optional offset.\n * Uses fractional indexing to minimize the number of changes when elements move positions.\n * Each element is assigned a fractional index that is lexicographically sortable.\n * When elements move, only the indices of the moved elements are updated, not all elements.\n *\n * @param comparator - A function that compares two elements\n * @param options - An optional object containing limit and offset properties\n * @returns A piped operator that orders the elements and limits the number of results\n */\nexport function topKWithFractionalIndex<KType, T>(\n comparator: (a: T, b: T) => number,\n options?: TopKWithFractionalIndexOptions\n): PipedOperator<[KType, T], [KType, IndexedValue<T>]> {\n const opts = options || {}\n\n return (\n stream: IStreamBuilder<[KType, T]>\n ): IStreamBuilder<[KType, IndexedValue<T>]> => {\n const output = new StreamBuilder<[KType, IndexedValue<T>]>(\n stream.graph,\n new DifferenceStreamWriter<[KType, IndexedValue<T>]>()\n )\n const operator = new TopKWithFractionalIndexOperator<KType, T>(\n stream.graph.getNextOperatorId(),\n stream.connectReader(),\n output.writer,\n comparator,\n opts\n )\n stream.graph.addOperator(operator)\n stream.graph.addStream(output.connectReader())\n return output\n }\n}\n\n// Abstraction for fractionally indexed values\nexport type FractionalIndex = string\nexport type IndexedValue<V> = [V, FractionalIndex]\n\nexport function indexedValue<V>(\n value: V,\n index: FractionalIndex\n): IndexedValue<V> {\n return [value, index]\n}\n\nexport function getValue<V>(indexedVal: IndexedValue<V>): V {\n return indexedVal[0]\n}\n\nexport function getIndex<V>(indexedVal: IndexedValue<V>): FractionalIndex {\n return indexedVal[1]\n}\n\nexport type Tag = number\nexport type TaggedValue<K, V> = [K, V, Tag]\n\nfunction tagValue<K, V>(key: K, value: V): TaggedValue<K, V> {\n return [key, value, globalObjectIdGenerator.getId(key)]\n}\n\nfunction getKey<K, V>(tieBreakerTaggedValue: TaggedValue<K, V>): K {\n return tieBreakerTaggedValue[0]\n}\n\nfunction getVal<K, V>(tieBreakerTaggedValue: TaggedValue<K, V>): V {\n return tieBreakerTaggedValue[1]\n}\n\nfunction getTag<K, V>(tieBreakerTaggedValue: TaggedValue<K, V>): Tag {\n return tieBreakerTaggedValue[2]\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/operators/index.ts
CHANGED
package/src/operators/orderBy.ts
CHANGED
|
@@ -12,6 +12,10 @@ export interface OrderByOptions<Ve> {
|
|
|
12
12
|
offset?: number
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
type OrderByWithFractionalIndexOptions<Ve> = OrderByOptions<Ve> & {
|
|
16
|
+
setSizeCallback?: (getSize: () => number) => void
|
|
17
|
+
}
|
|
18
|
+
|
|
15
19
|
/**
|
|
16
20
|
* Orders the elements and limits the number of results, with optional offset
|
|
17
21
|
* This requires a keyed stream, and uses the `topK` operator to order all the elements.
|
|
@@ -136,13 +140,14 @@ export function orderByWithFractionalIndexBase<
|
|
|
136
140
|
valueExtractor: (
|
|
137
141
|
value: T extends KeyValue<unknown, infer V> ? V : never
|
|
138
142
|
) => Ve,
|
|
139
|
-
options?:
|
|
143
|
+
options?: OrderByWithFractionalIndexOptions<Ve>
|
|
140
144
|
) {
|
|
141
145
|
type KeyType = T extends KeyValue<infer K, unknown> ? K : never
|
|
142
146
|
type ValueType = T extends KeyValue<unknown, infer V> ? V : never
|
|
143
147
|
|
|
144
148
|
const limit = options?.limit ?? Infinity
|
|
145
149
|
const offset = options?.offset ?? 0
|
|
150
|
+
const setSizeCallback = options?.setSizeCallback
|
|
146
151
|
const comparator =
|
|
147
152
|
options?.comparator ??
|
|
148
153
|
((a, b) => {
|
|
@@ -162,6 +167,7 @@ export function orderByWithFractionalIndexBase<
|
|
|
162
167
|
{
|
|
163
168
|
limit,
|
|
164
169
|
offset,
|
|
170
|
+
setSizeCallback,
|
|
165
171
|
}
|
|
166
172
|
),
|
|
167
173
|
consolidate()
|
|
@@ -185,7 +191,7 @@ export function orderByWithFractionalIndex<
|
|
|
185
191
|
valueExtractor: (
|
|
186
192
|
value: T extends KeyValue<unknown, infer V> ? V : never
|
|
187
193
|
) => Ve,
|
|
188
|
-
options?:
|
|
194
|
+
options?: OrderByWithFractionalIndexOptions<Ve>
|
|
189
195
|
) {
|
|
190
196
|
return orderByWithFractionalIndexBase(
|
|
191
197
|
topKWithFractionalIndex,
|
|
@@ -0,0 +1,53 @@
|
|
|
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 TapOperator<T> extends LinearUnaryOperator<T, T> {
|
|
11
|
+
#f: (data: T) => void
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
id: number,
|
|
15
|
+
inputA: DifferenceStreamReader<T>,
|
|
16
|
+
output: DifferenceStreamWriter<T>,
|
|
17
|
+
f: (data: T) => void
|
|
18
|
+
) {
|
|
19
|
+
super(id, inputA, output)
|
|
20
|
+
this.#f = f
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
inner(collection: MultiSet<T>): MultiSet<T> {
|
|
24
|
+
return collection.map((data) => {
|
|
25
|
+
this.#f(data)
|
|
26
|
+
return data
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Invokes a function for each element in the input stream.
|
|
33
|
+
* This operator doesn't modify the stream and is used to perform side effects.
|
|
34
|
+
* @param f - The function to invoke on each element
|
|
35
|
+
* @returns The input stream
|
|
36
|
+
*/
|
|
37
|
+
export function tap<T>(f: (data: T) => void): PipedOperator<T, T> {
|
|
38
|
+
return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {
|
|
39
|
+
const output = new StreamBuilder<T>(
|
|
40
|
+
stream.graph,
|
|
41
|
+
new DifferenceStreamWriter<T>()
|
|
42
|
+
)
|
|
43
|
+
const operator = new TapOperator<T>(
|
|
44
|
+
stream.graph.getNextOperatorId(),
|
|
45
|
+
stream.connectReader(),
|
|
46
|
+
output.writer,
|
|
47
|
+
f
|
|
48
|
+
)
|
|
49
|
+
stream.graph.addOperator(operator)
|
|
50
|
+
stream.graph.addStream(output.connectReader())
|
|
51
|
+
return output
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -9,6 +9,7 @@ import type { IStreamBuilder, PipedOperator } from "../types.js"
|
|
|
9
9
|
export interface TopKWithFractionalIndexOptions {
|
|
10
10
|
limit?: number
|
|
11
11
|
offset?: number
|
|
12
|
+
setSizeCallback?: (getSize: () => number) => void
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export type TopKChanges<V> = {
|
|
@@ -23,6 +24,7 @@ export type TopKChanges<V> = {
|
|
|
23
24
|
* and returns changes to the topK.
|
|
24
25
|
*/
|
|
25
26
|
export interface TopK<V> {
|
|
27
|
+
size: number
|
|
26
28
|
insert: (value: V) => TopKChanges<V>
|
|
27
29
|
delete: (value: V) => TopKChanges<V>
|
|
28
30
|
}
|
|
@@ -49,6 +51,13 @@ class TopKArray<V> implements TopK<V> {
|
|
|
49
51
|
this.#comparator = comparator
|
|
50
52
|
}
|
|
51
53
|
|
|
54
|
+
get size(): number {
|
|
55
|
+
const offset = this.#topKStart
|
|
56
|
+
const limit = this.#topKEnd - this.#topKStart
|
|
57
|
+
const available = this.#sortedValues.length - offset
|
|
58
|
+
return Math.max(0, Math.min(limit, available))
|
|
59
|
+
}
|
|
60
|
+
|
|
52
61
|
insert(value: V): TopKChanges<V> {
|
|
53
62
|
const result: TopKChanges<V> = { moveIn: null, moveOut: null }
|
|
54
63
|
|
|
@@ -169,6 +178,8 @@ export class TopKWithFractionalIndexOperator<K, T> extends UnaryOperator<
|
|
|
169
178
|
*/
|
|
170
179
|
#topK: TopK<TaggedValue<K, T>>
|
|
171
180
|
|
|
181
|
+
#limit: number
|
|
182
|
+
|
|
172
183
|
constructor(
|
|
173
184
|
id: number,
|
|
174
185
|
inputA: DifferenceStreamReader<[K, T]>,
|
|
@@ -177,7 +188,7 @@ export class TopKWithFractionalIndexOperator<K, T> extends UnaryOperator<
|
|
|
177
188
|
options: TopKWithFractionalIndexOptions
|
|
178
189
|
) {
|
|
179
190
|
super(id, inputA, output)
|
|
180
|
-
|
|
191
|
+
this.#limit = options.limit ?? Infinity
|
|
181
192
|
const offset = options.offset ?? 0
|
|
182
193
|
const compareTaggedValues = (
|
|
183
194
|
a: TaggedValue<K, T>,
|
|
@@ -193,7 +204,8 @@ export class TopKWithFractionalIndexOperator<K, T> extends UnaryOperator<
|
|
|
193
204
|
const tieBreakerB = getTag(b)
|
|
194
205
|
return tieBreakerA - tieBreakerB
|
|
195
206
|
}
|
|
196
|
-
this.#topK = this.createTopK(offset, limit, compareTaggedValues)
|
|
207
|
+
this.#topK = this.createTopK(offset, this.#limit, compareTaggedValues)
|
|
208
|
+
options.setSizeCallback?.(() => this.#topK.size)
|
|
197
209
|
}
|
|
198
210
|
|
|
199
211
|
protected createTopK(
|
|
@@ -75,6 +75,13 @@ class TopKTree<V> implements TopK<V> {
|
|
|
75
75
|
this.#tree = new BTree(undefined, comparator)
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
get size(): number {
|
|
79
|
+
const offset = this.#topKStart
|
|
80
|
+
const limit = this.#topKEnd - this.#topKStart
|
|
81
|
+
const available = this.#tree.size - offset
|
|
82
|
+
return Math.max(0, Math.min(limit, available))
|
|
83
|
+
}
|
|
84
|
+
|
|
78
85
|
/**
|
|
79
86
|
* Insert a *new* value.
|
|
80
87
|
* Ignores the value if it is already present.
|