@tanstack/db-ivm 0.1.0 → 0.1.1
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/operators/orderBy.cjs +6 -19
- package/dist/cjs/operators/orderBy.cjs.map +1 -1
- package/dist/cjs/operators/orderBy.d.cts +2 -2
- package/dist/cjs/operators/orderByBTree.d.cts +1 -1
- package/dist/cjs/operators/topKWithFractionalIndex.cjs +36 -19
- package/dist/cjs/operators/topKWithFractionalIndex.cjs.map +1 -1
- package/dist/cjs/operators/topKWithFractionalIndex.d.cts +11 -14
- package/dist/cjs/operators/topKWithFractionalIndexBTree.d.cts +5 -5
- package/dist/esm/operators/orderBy.d.ts +2 -2
- package/dist/esm/operators/orderBy.js +6 -19
- package/dist/esm/operators/orderBy.js.map +1 -1
- package/dist/esm/operators/orderByBTree.d.ts +1 -1
- package/dist/esm/operators/topKWithFractionalIndex.d.ts +11 -14
- package/dist/esm/operators/topKWithFractionalIndex.js +36 -19
- package/dist/esm/operators/topKWithFractionalIndex.js.map +1 -1
- package/dist/esm/operators/topKWithFractionalIndexBTree.d.ts +5 -5
- package/package.json +1 -1
- package/src/operators/orderBy.ts +11 -29
- package/src/operators/topKWithFractionalIndex.ts +69 -57
- package/src/operators/topKWithFractionalIndexBTree.ts +14 -19
|
@@ -77,26 +77,13 @@ function orderByWithFractionalIndexBase(topKFunction, valueExtractor, options) {
|
|
|
77
77
|
});
|
|
78
78
|
return (stream) => {
|
|
79
79
|
return stream.pipe(
|
|
80
|
-
|
|
81
|
-
(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
value
|
|
87
|
-
)
|
|
88
|
-
]
|
|
89
|
-
]
|
|
80
|
+
topKFunction(
|
|
81
|
+
(a, b) => comparator(valueExtractor(a), valueExtractor(b)),
|
|
82
|
+
{
|
|
83
|
+
limit,
|
|
84
|
+
offset
|
|
85
|
+
}
|
|
90
86
|
),
|
|
91
|
-
topKFunction((a, b) => comparator(a[1], b[1]), {
|
|
92
|
-
limit,
|
|
93
|
-
offset
|
|
94
|
-
}),
|
|
95
|
-
map.map(([_, [[key], index]]) => [key, index]),
|
|
96
|
-
join.innerJoin(stream),
|
|
97
|
-
map.map(([key, [index, value]]) => {
|
|
98
|
-
return [key, [value, index]];
|
|
99
|
-
}),
|
|
100
87
|
consolidate.consolidate()
|
|
101
88
|
);
|
|
102
89
|
};
|
|
@@ -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?: 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
|
|
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?: OrderByOptions<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 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 }\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?: OrderByOptions<Ve>\n) {\n return orderByWithFractionalIndexBase(\n topKWithFractionalIndex,\n valueExtractor,\n options\n )\n}\n"],"names":["map","topK","innerJoin","consolidate","topKWithIndex","topKWithFractionalIndex"],"mappings":";;;;;;;AAsBO,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,MACZA,IAAAA;AAAAA,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,MAEJC,KAAAA,KAAK,CAAC,GAAG,MAAM,WAAW,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,OAAO,QAAQ;AAAA,MACxDD,QAAI,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,CAA4B;AAAA,MAC1DE,KAAAA,UAAU,MAAM;AAAA,MAChBF,IAAAA,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM;AACpB,eAAO,CAAC,KAAK,MAAM,CAAC,CAAC;AAAA,MACvB,CAAC;AAAA,MACDG,YAAAA,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,MACZH,IAAAA;AAAAA,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,MAEJI,KAAAA,cAAc,CAAC,GAAG,MAAM,WAAW,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,OAAO,QAAQ;AAAA,MACjEJ,IAAAA,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,KAAK,CAA8B;AAAA,MACtEE,KAAAA,UAAU,MAAM;AAAA,MAChBF,IAAAA,IAAI,CAAC,CAAC,KAAK,CAAC,OAAO,KAAK,CAAC,MAAM;AAC7B,eAAO,CAAC,KAAK,CAAC,OAAO,KAAK,CAAC;AAAA,MAC7B,CAAC;AAAA,MACDG,YAAAA,YAAA;AAAA,IAAY;AAAA,EAEhB;AACF;AAEO,SAAS,+BAId,cACA,gBAGA,SACA;AAIA,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,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,QAAA;AAAA,MACF;AAAA,MAEFA,YAAAA,YAAA;AAAA,IAAY;AAAA,EAEhB;AACF;AAWO,SAAS,2BAId,gBAGA,SACA;AACA,SAAO;AAAA,IACLE,wBAAAA;AAAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;;;;;"}
|
|
@@ -25,7 +25,7 @@ export declare function orderBy<T extends KeyValue<unknown, unknown>, Ve = unkno
|
|
|
25
25
|
* @returns A piped operator that orders the elements and limits the number of results
|
|
26
26
|
*/
|
|
27
27
|
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?: OrderByOptions<Ve>): (stream: IStreamBuilder<T>) => IStreamBuilder<
|
|
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?: OrderByOptions<Ve>): (stream: IStreamBuilder<T>) => IStreamBuilder<[T extends KeyValue<infer K, unknown> ? K : never, [T extends KeyValue<unknown, infer V> ? V : never, string]]>;
|
|
29
29
|
/**
|
|
30
30
|
* Orders the elements and limits the number of results, with optional offset and
|
|
31
31
|
* annotates the value with a fractional index.
|
|
@@ -35,4 +35,4 @@ export declare function orderByWithFractionalIndexBase<T extends KeyValue<unknow
|
|
|
35
35
|
* @param options - An optional object containing comparator, limit and offset properties
|
|
36
36
|
* @returns A piped operator that orders the elements and limits the number of results
|
|
37
37
|
*/
|
|
38
|
-
export declare function orderByWithFractionalIndex<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<
|
|
38
|
+
export declare function orderByWithFractionalIndex<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<[T extends KeyValue<infer K, unknown> ? K : never, [T extends KeyValue<unknown, infer V> ? V : never, string]]>;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { KeyValue } from '../types.js';
|
|
2
2
|
import { OrderByOptions } from './orderBy.js';
|
|
3
|
-
export declare function orderByWithFractionalIndexBTree<T extends KeyValue<unknown, unknown>, Ve = unknown>(valueExtractor: (value: T extends KeyValue<unknown, infer V> ? V : never) => Ve, options?: OrderByOptions<Ve>): (stream: import('../types.js').IStreamBuilder<T>) => import('../types.js').IStreamBuilder<
|
|
3
|
+
export declare function orderByWithFractionalIndexBTree<T extends KeyValue<unknown, unknown>, Ve = unknown>(valueExtractor: (value: T extends KeyValue<unknown, infer V> ? V : never) => Ve, options?: OrderByOptions<Ve>): (stream: import('../types.js').IStreamBuilder<T>) => import('../types.js').IStreamBuilder<[T extends KeyValue<infer K, unknown> ? K : never, [T extends KeyValue<unknown, infer V> ? V : never, string]]>;
|
|
@@ -13,7 +13,6 @@ const fractionalIndexing = require("fractional-indexing");
|
|
|
13
13
|
const graph = require("../graph.cjs");
|
|
14
14
|
const d2 = require("../d2.cjs");
|
|
15
15
|
const multiset = require("../multiset.cjs");
|
|
16
|
-
const indexes = require("../indexes.cjs");
|
|
17
16
|
const utils = require("../utils.cjs");
|
|
18
17
|
class TopKArray {
|
|
19
18
|
constructor(offset, limit, comparator) {
|
|
@@ -91,7 +90,8 @@ findIndex_fn = function(value) {
|
|
|
91
90
|
class TopKWithFractionalIndexOperator extends graph.UnaryOperator {
|
|
92
91
|
constructor(id, inputA, output, comparator, options) {
|
|
93
92
|
super(id, inputA, output);
|
|
94
|
-
__privateAdd(this, _index, new
|
|
93
|
+
__privateAdd(this, _index, /* @__PURE__ */ new Map());
|
|
94
|
+
// maps keys to their multiplicity
|
|
95
95
|
/**
|
|
96
96
|
* topK data structure that supports insertions and deletions
|
|
97
97
|
* and returns changes to the topK.
|
|
@@ -100,7 +100,7 @@ class TopKWithFractionalIndexOperator extends graph.UnaryOperator {
|
|
|
100
100
|
const limit = options.limit ?? Infinity;
|
|
101
101
|
const offset = options.offset ?? 0;
|
|
102
102
|
const compareTaggedValues = (a, b) => {
|
|
103
|
-
const valueComparison = comparator(
|
|
103
|
+
const valueComparison = comparator(getVal(a), getVal(b));
|
|
104
104
|
if (valueComparison !== 0) {
|
|
105
105
|
return valueComparison;
|
|
106
106
|
}
|
|
@@ -126,30 +126,47 @@ class TopKWithFractionalIndexOperator extends graph.UnaryOperator {
|
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
processElement(key, value, multiplicity, result) {
|
|
129
|
-
const oldMultiplicity =
|
|
130
|
-
__privateGet(this, _index).addValue(key, [value, multiplicity]);
|
|
131
|
-
const newMultiplicity = __privateGet(this, _index).getMultiplicity(key, value);
|
|
129
|
+
const { oldMultiplicity, newMultiplicity } = this.addKey(key, multiplicity);
|
|
132
130
|
let res = {
|
|
133
131
|
moveIn: null,
|
|
134
132
|
moveOut: null
|
|
135
133
|
};
|
|
136
134
|
if (oldMultiplicity <= 0 && newMultiplicity > 0) {
|
|
137
|
-
const taggedValue = tagValue(value);
|
|
135
|
+
const taggedValue = tagValue(key, value);
|
|
138
136
|
res = __privateGet(this, _topK).insert(taggedValue);
|
|
139
137
|
} else if (oldMultiplicity > 0 && newMultiplicity <= 0) {
|
|
140
|
-
const taggedValue = tagValue(value);
|
|
138
|
+
const taggedValue = tagValue(key, value);
|
|
141
139
|
res = __privateGet(this, _topK).delete(taggedValue);
|
|
142
140
|
} else ;
|
|
143
141
|
if (res.moveIn) {
|
|
144
|
-
const
|
|
145
|
-
|
|
142
|
+
const index = getIndex(res.moveIn);
|
|
143
|
+
const taggedValue = getValue(res.moveIn);
|
|
144
|
+
const k = getKey(taggedValue);
|
|
145
|
+
const val = getVal(taggedValue);
|
|
146
|
+
result.push([[k, [val, index]], 1]);
|
|
146
147
|
}
|
|
147
148
|
if (res.moveOut) {
|
|
148
|
-
const
|
|
149
|
-
|
|
149
|
+
const index = getIndex(res.moveOut);
|
|
150
|
+
const taggedValue = getValue(res.moveOut);
|
|
151
|
+
const k = getKey(taggedValue);
|
|
152
|
+
const val = getVal(taggedValue);
|
|
153
|
+
result.push([[k, [val, index]], -1]);
|
|
150
154
|
}
|
|
151
155
|
return;
|
|
152
156
|
}
|
|
157
|
+
getMultiplicity(key) {
|
|
158
|
+
return __privateGet(this, _index).get(key) ?? 0;
|
|
159
|
+
}
|
|
160
|
+
addKey(key, multiplicity) {
|
|
161
|
+
const oldMultiplicity = this.getMultiplicity(key);
|
|
162
|
+
const newMultiplicity = oldMultiplicity + multiplicity;
|
|
163
|
+
if (newMultiplicity === 0) {
|
|
164
|
+
__privateGet(this, _index).delete(key);
|
|
165
|
+
} else {
|
|
166
|
+
__privateGet(this, _index).set(key, newMultiplicity);
|
|
167
|
+
}
|
|
168
|
+
return { oldMultiplicity, newMultiplicity };
|
|
169
|
+
}
|
|
153
170
|
}
|
|
154
171
|
_index = new WeakMap();
|
|
155
172
|
_topK = new WeakMap();
|
|
@@ -181,18 +198,18 @@ function getValue(indexedVal) {
|
|
|
181
198
|
function getIndex(indexedVal) {
|
|
182
199
|
return indexedVal[1];
|
|
183
200
|
}
|
|
184
|
-
function
|
|
185
|
-
return [
|
|
186
|
-
}
|
|
187
|
-
function tagValue(value) {
|
|
188
|
-
return [value, utils.globalObjectIdGenerator.getId(value)];
|
|
201
|
+
function tagValue(key, value) {
|
|
202
|
+
return [key, value, utils.globalObjectIdGenerator.getId(key)];
|
|
189
203
|
}
|
|
190
|
-
function
|
|
204
|
+
function getKey(tieBreakerTaggedValue) {
|
|
191
205
|
return tieBreakerTaggedValue[0];
|
|
192
206
|
}
|
|
193
|
-
function
|
|
207
|
+
function getVal(tieBreakerTaggedValue) {
|
|
194
208
|
return tieBreakerTaggedValue[1];
|
|
195
209
|
}
|
|
210
|
+
function getTag(tieBreakerTaggedValue) {
|
|
211
|
+
return tieBreakerTaggedValue[2];
|
|
212
|
+
}
|
|
196
213
|
exports.TopKWithFractionalIndexOperator = TopKWithFractionalIndexOperator;
|
|
197
214
|
exports.getIndex = getIndex;
|
|
198
215
|
exports.getValue = getValue;
|
|
@@ -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 { Index } from \"../indexes.js\"\nimport { binarySearch, globalObjectIdGenerator } from \"../utils.js\"\nimport type { DifferenceStreamReader } from \"../graph.js\"\nimport type { IStreamBuilder, KeyValue, 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, V1> extends UnaryOperator<\n [K, V1],\n [K, IndexedValue<V1>]\n> {\n #index = new Index<K, V1>()\n\n /**\n * topK data structure that supports insertions and deletions\n * and returns changes to the topK.\n */\n #topK: TopK<TaggedValue<V1>>\n\n constructor(\n id: number,\n inputA: DifferenceStreamReader<[K, V1]>,\n output: DifferenceStreamWriter<[K, [V1, string]]>,\n comparator: (a: V1, b: V1) => 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 = (a: TaggedValue<V1>, b: TaggedValue<V1>) => {\n // First compare on the value\n const valueComparison = comparator(untagValue(a), untagValue(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<V1>, b: TaggedValue<V1>) => number\n ): TopK<TaggedValue<V1>> {\n return new TopKArray(offset, limit, comparator)\n }\n\n run(): void {\n const result: Array<[[K, [V1, string]], 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: V1,\n multiplicity: number,\n result: Array<[[K, [V1, string]], number]>\n ): void {\n const oldMultiplicity = this.#index.getMultiplicity(key, value)\n this.#index.addValue(key, [value, multiplicity])\n const newMultiplicity = this.#index.getMultiplicity(key, value)\n\n let res: TopKChanges<TaggedValue<V1>> = {\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(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(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 valueWithoutTieBreaker = mapValue(res.moveIn, untagValue)\n result.push([[key, valueWithoutTieBreaker], 1])\n }\n\n if (res.moveOut) {\n const valueWithoutTieBreaker = mapValue(res.moveOut, untagValue)\n result.push([[key, valueWithoutTieBreaker], -1])\n }\n\n return\n }\n}\n\n/**\n * Limits the number of results based on a comparator, with optional offset.\n * This works on a keyed stream, where the key is the first element of the tuple.\n * The ordering is within a key group, i.e. elements are sorted within a key group\n * and the limit + offset is applied to that sorted group.\n * To order the entire stream, key by the same value for all elements such as null.\n *\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<\n KType extends T extends KeyValue<infer K, infer _V> ? K : never,\n V1Type extends T extends KeyValue<KType, infer V> ? V : never,\n T,\n>(\n comparator: (a: V1Type, b: V1Type) => number,\n options?: TopKWithFractionalIndexOptions\n): PipedOperator<T, KeyValue<KType, [V1Type, string]>> {\n const opts = options || {}\n\n return (\n stream: IStreamBuilder<T>\n ): IStreamBuilder<KeyValue<KType, [V1Type, string]>> => {\n const output = new StreamBuilder<KeyValue<KType, [V1Type, string]>>(\n stream.graph,\n new DifferenceStreamWriter<KeyValue<KType, [V1Type, string]>>()\n )\n const operator = new TopKWithFractionalIndexOperator<KType, V1Type>(\n stream.graph.getNextOperatorId(),\n stream.connectReader() as DifferenceStreamReader<KeyValue<KType, V1Type>>,\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\nfunction mapValue<V, W>(\n indexedVal: IndexedValue<V>,\n f: (value: V) => W\n): IndexedValue<W> {\n return [f(getValue(indexedVal)), getIndex(indexedVal)]\n}\n\nexport type Tag = number\nexport type TaggedValue<V> = [V, Tag]\n\nfunction tagValue<V>(value: V): TaggedValue<V> {\n return [value, globalObjectIdGenerator.getId(value)]\n}\n\nfunction untagValue<V>(tieBreakerTaggedValue: TaggedValue<V>): V {\n return tieBreakerTaggedValue[0]\n}\n\nfunction getTag<V>(tieBreakerTaggedValue: TaggedValue<V>): Tag {\n return tieBreakerTaggedValue[1]\n}\n"],"names":["generateKeyBetween","binarySearch","UnaryOperator","Index","MultiSet","StreamBuilder","DifferenceStreamWriter","globalObjectIdGenerator"],"mappings":";;;;;;;;;;;;;;;;;AAoCA,MAAM,UAAgC;AAAA,EAMpC,YACE,QACA,OACA,YACA;AAVJ;AACE,sCAAwC,CAAA;AACxC;AACA;AACA;AAOE,uBAAK,YAAa;AAClB,uBAAK,UAAW,SAAS;AACzB,uBAAK,aAAc;AAAA,EACrB;AAAA,EAEA,OAAO,OAA0B;AAC/B,UAAM,SAAyB,EAAE,QAAQ,MAAM,SAAS,KAAA;AAGxD,UAAM,QAAQ,sBAAK,oCAAL,WAAgB;AAE9B,UAAM,cACJ,UAAU,IAAI,OAAO,SAAS,mBAAK,eAAc,QAAQ,CAAC,CAAE;AAC9D,UAAM,aACJ,UAAU,mBAAK,eAAc,SACzB,OACA,SAAS,mBAAK,eAAc,KAAK,CAAE;AACzC,UAAM,kBAAkBA,mBAAAA,mBAAmB,aAAa,UAAU;AAGlE,UAAM,MAAM,aAAa,OAAO,eAAe;AAE/C,uBAAK,eAAc,OAAO,OAAO,GAAG,GAAG;AAGvC,QAAI,QAAQ,mBAAK,WAAU;AAKzB,YAAM,cAAc,KAAK,IAAI,OAAO,mBAAK,WAAU;AACnD,UAAI,cAAc,mBAAK,eAAc,QAAQ;AAI3C,eAAO,SAAS,mBAAK,eAAc,WAAW;AAK9C,YAAI,mBAAK,YAAW,mBAAK,eAAc,QAAQ;AAC7C,iBAAO,UAAU,mBAAK,eAAc,mBAAK,SAAQ;AAAA,QACnD;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,OAAO,OAA0B;AAC/B,UAAM,SAAyB,EAAE,QAAQ,MAAM,SAAS,KAAA;AAGxD,UAAM,QAAQ,sBAAK,oCAAL,WAAgB;AAE9B,UAAM,CAAC,WAAW,IAAI,mBAAK,eAAc,OAAO,OAAO,CAAC;AAGxD,QAAI,QAAQ,mBAAK,WAAU;AAIzB,aAAO,UAAU;AACjB,UAAI,QAAQ,mBAAK,aAAY;AAM3B,cAAM,eAAe,mBAAK,cAAa;AACvC,YAAI,eAAe,mBAAK,eAAc,QAAQ;AAC5C,iBAAO,UAAU,mBAAK,eAAc,YAAY;AAAA,QAClD,OAAO;AAGL,iBAAO,UAAU;AAAA,QACnB;AAAA,MACF;AAKA,YAAM,cAAc,mBAAK,YAAW;AACpC,UAAI,cAAc,mBAAK,eAAc,QAAQ;AAC3C,eAAO,SAAS,mBAAK,eAAc,WAAW;AAAA,MAChD;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAWF;AApHE;AACA;AACA;AACA;AAJF;AAAA;AAAA;AAAA;AAgHE,wBAAW,OAAkB;AAC3B,SAAOC,MAAAA;AAAAA,IAAa,mBAAK;AAAA,IAAe,aAAa,OAAO,EAAE;AAAA,IAAG,CAAC,GAAG,MACnE,mBAAK,aAAL,WAAiB,SAAS,CAAC,GAAG,SAAS,CAAC;AAAA,EAAC;AAE7C;AAQK,MAAM,wCAA+CC,MAAAA,cAG1D;AAAA,EASA,YACE,IACA,QACA,QACA,YACA,SACA;AACA,UAAM,IAAI,QAAQ,MAAM;AAf1B,+BAAS,IAAIC,QAAAA,MAAA;AAMb;AAAA;AAAA;AAAA;AAAA;AAUE,UAAM,QAAQ,QAAQ,SAAS;AAC/B,UAAM,SAAS,QAAQ,UAAU;AACjC,UAAM,sBAAsB,CAAC,GAAoB,MAAuB;AAEtE,YAAM,kBAAkB,WAAW,WAAW,CAAC,GAAG,WAAW,CAAC,CAAC;AAC/D,UAAI,oBAAoB,GAAG;AACzB,eAAO;AAAA,MACT;AAEA,YAAM,cAAc,OAAO,CAAC;AAC5B,YAAM,cAAc,OAAO,CAAC;AAC5B,aAAO,cAAc;AAAA,IACvB;AACA,uBAAK,OAAQ,KAAK,WAAW,QAAQ,OAAO,mBAAmB;AAAA,EACjE;AAAA,EAEU,WACR,QACA,OACA,YACuB;AACvB,WAAO,IAAI,UAAU,QAAQ,OAAO,UAAU;AAAA,EAChD;AAAA,EAEA,MAAY;AACV,UAAM,SAA6C,CAAA;AACnD,eAAW,WAAW,KAAK,iBAAiB;AAC1C,iBAAW,CAAC,MAAM,YAAY,KAAK,QAAQ,YAAY;AACrD,cAAM,CAAC,KAAK,KAAK,IAAI;AACrB,aAAK,eAAe,KAAK,OAAO,cAAc,MAAM;AAAA,MACtD;AAAA,IACF;AAEA,QAAI,OAAO,SAAS,GAAG;AACrB,WAAK,OAAO,SAAS,IAAIC,SAAAA,SAAS,MAAM,CAAC;AAAA,IAC3C;AAAA,EACF;AAAA,EAEA,eACE,KACA,OACA,cACA,QACM;AACN,UAAM,kBAAkB,mBAAK,QAAO,gBAAgB,KAAK,KAAK;AAC9D,uBAAK,QAAO,SAAS,KAAK,CAAC,OAAO,YAAY,CAAC;AAC/C,UAAM,kBAAkB,mBAAK,QAAO,gBAAgB,KAAK,KAAK;AAE9D,QAAI,MAAoC;AAAA,MACtC,QAAQ;AAAA,MACR,SAAS;AAAA,IAAA;AAEX,QAAI,mBAAmB,KAAK,kBAAkB,GAAG;AAG/C,YAAM,cAAc,SAAS,KAAK;AAClC,YAAM,mBAAK,OAAM,OAAO,WAAW;AAAA,IACrC,WAAW,kBAAkB,KAAK,mBAAmB,GAAG;AAGtD,YAAM,cAAc,SAAS,KAAK;AAClC,YAAM,mBAAK,OAAM,OAAO,WAAW;AAAA,IACrC,MAAO;AAMP,QAAI,IAAI,QAAQ;AACd,YAAM,yBAAyB,SAAS,IAAI,QAAQ,UAAU;AAC9D,aAAO,KAAK,CAAC,CAAC,KAAK,sBAAsB,GAAG,CAAC,CAAC;AAAA,IAChD;AAEA,QAAI,IAAI,SAAS;AACf,YAAM,yBAAyB,SAAS,IAAI,SAAS,UAAU;AAC/D,aAAO,KAAK,CAAC,CAAC,KAAK,sBAAsB,GAAG,EAAE,CAAC;AAAA,IACjD;AAEA;AAAA,EACF;AACF;AAhGE;AAMA;AA2GK,SAAS,wBAKd,YACA,SACqD;AACrD,QAAM,OAAO,WAAW,CAAA;AAExB,SAAO,CACL,WACsD;AACtD,UAAM,SAAS,IAAIC,GAAAA;AAAAA,MACjB,OAAO;AAAA,MACP,IAAIC,MAAAA,uBAAA;AAAA,IAA0D;AAEhE,UAAM,WAAW,IAAI;AAAA,MACnB,OAAO,MAAM,kBAAA;AAAA,MACb,OAAO,cAAA;AAAA,MACP,OAAO;AAAA,MACP;AAAA,MACA;AAAA,IAAA;AAEF,WAAO,MAAM,YAAY,QAAQ;AACjC,WAAO,MAAM,UAAU,OAAO,cAAA,CAAe;AAC7C,WAAO;AAAA,EACT;AACF;AAMO,SAAS,aACd,OACA,OACiB;AACjB,SAAO,CAAC,OAAO,KAAK;AACtB;AAEO,SAAS,SAAY,YAAgC;AAC1D,SAAO,WAAW,CAAC;AACrB;AAEO,SAAS,SAAY,YAA8C;AACxE,SAAO,WAAW,CAAC;AACrB;AAEA,SAAS,SACP,YACA,GACiB;AACjB,SAAO,CAAC,EAAE,SAAS,UAAU,CAAC,GAAG,SAAS,UAAU,CAAC;AACvD;AAKA,SAAS,SAAY,OAA0B;AAC7C,SAAO,CAAC,OAAOC,MAAAA,wBAAwB,MAAM,KAAK,CAAC;AACrD;AAEA,SAAS,WAAc,uBAA0C;AAC/D,SAAO,sBAAsB,CAAC;AAChC;AAEA,SAAS,OAAU,uBAA4C;AAC7D,SAAO,sBAAsB,CAAC;AAChC;;;;;;"}
|
|
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,5 +1,5 @@
|
|
|
1
1
|
import { DifferenceStreamWriter, UnaryOperator, DifferenceStreamReader } from '../graph.js';
|
|
2
|
-
import {
|
|
2
|
+
import { PipedOperator } from '../types.js';
|
|
3
3
|
export interface TopKWithFractionalIndexOptions {
|
|
4
4
|
limit?: number;
|
|
5
5
|
offset?: number;
|
|
@@ -23,26 +23,23 @@ export interface TopK<V> {
|
|
|
23
23
|
* This operator maintains fractional indices for sorted elements
|
|
24
24
|
* and only updates indices when elements move position
|
|
25
25
|
*/
|
|
26
|
-
export declare class TopKWithFractionalIndexOperator<K,
|
|
26
|
+
export declare class TopKWithFractionalIndexOperator<K, T> extends UnaryOperator<[
|
|
27
27
|
K,
|
|
28
|
-
|
|
28
|
+
T
|
|
29
29
|
], [
|
|
30
30
|
K,
|
|
31
|
-
IndexedValue<
|
|
31
|
+
IndexedValue<T>
|
|
32
32
|
]> {
|
|
33
33
|
#private;
|
|
34
|
-
constructor(id: number, inputA: DifferenceStreamReader<[K,
|
|
35
|
-
protected createTopK(offset: number, limit: number, comparator: (a: TaggedValue<
|
|
34
|
+
constructor(id: number, inputA: DifferenceStreamReader<[K, T]>, output: DifferenceStreamWriter<[K, IndexedValue<T>]>, comparator: (a: T, b: T) => number, options: TopKWithFractionalIndexOptions);
|
|
35
|
+
protected createTopK(offset: number, limit: number, comparator: (a: TaggedValue<K, T>, b: TaggedValue<K, T>) => number): TopK<TaggedValue<K, T>>;
|
|
36
36
|
run(): void;
|
|
37
|
-
processElement(key: K, value:
|
|
37
|
+
processElement(key: K, value: T, multiplicity: number, result: Array<[[K, IndexedValue<T>], number]>): void;
|
|
38
|
+
private getMultiplicity;
|
|
39
|
+
private addKey;
|
|
38
40
|
}
|
|
39
41
|
/**
|
|
40
42
|
* Limits the number of results based on a comparator, with optional offset.
|
|
41
|
-
* This works on a keyed stream, where the key is the first element of the tuple.
|
|
42
|
-
* The ordering is within a key group, i.e. elements are sorted within a key group
|
|
43
|
-
* and the limit + offset is applied to that sorted group.
|
|
44
|
-
* To order the entire stream, key by the same value for all elements such as null.
|
|
45
|
-
*
|
|
46
43
|
* Uses fractional indexing to minimize the number of changes when elements move positions.
|
|
47
44
|
* Each element is assigned a fractional index that is lexicographically sortable.
|
|
48
45
|
* When elements move, only the indices of the moved elements are updated, not all elements.
|
|
@@ -51,11 +48,11 @@ export declare class TopKWithFractionalIndexOperator<K, V1> extends UnaryOperato
|
|
|
51
48
|
* @param options - An optional object containing limit and offset properties
|
|
52
49
|
* @returns A piped operator that orders the elements and limits the number of results
|
|
53
50
|
*/
|
|
54
|
-
export declare function topKWithFractionalIndex<KType
|
|
51
|
+
export declare function topKWithFractionalIndex<KType, T>(comparator: (a: T, b: T) => number, options?: TopKWithFractionalIndexOptions): PipedOperator<[KType, T], [KType, IndexedValue<T>]>;
|
|
55
52
|
export type FractionalIndex = string;
|
|
56
53
|
export type IndexedValue<V> = [V, FractionalIndex];
|
|
57
54
|
export declare function indexedValue<V>(value: V, index: FractionalIndex): IndexedValue<V>;
|
|
58
55
|
export declare function getValue<V>(indexedVal: IndexedValue<V>): V;
|
|
59
56
|
export declare function getIndex<V>(indexedVal: IndexedValue<V>): FractionalIndex;
|
|
60
57
|
export type Tag = number;
|
|
61
|
-
export type TaggedValue<V> = [V, Tag];
|
|
58
|
+
export type TaggedValue<K, V> = [K, V, Tag];
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { TopKWithFractionalIndexOperator, TaggedValue, TopK, TopKWithFractionalIndexOptions } from './topKWithFractionalIndex.js';
|
|
2
|
-
import {
|
|
1
|
+
import { TopKWithFractionalIndexOperator, IndexedValue, TaggedValue, TopK, TopKWithFractionalIndexOptions } from './topKWithFractionalIndex.js';
|
|
2
|
+
import { PipedOperator } from '../types.js';
|
|
3
3
|
export declare function loadBTree(): Promise<void>;
|
|
4
4
|
/**
|
|
5
5
|
* Operator for fractional indexed topK operations
|
|
6
6
|
* This operator maintains fractional indices for sorted elements
|
|
7
7
|
* and only updates indices when elements move position
|
|
8
8
|
*/
|
|
9
|
-
export declare class TopKWithFractionalIndexBTreeOperator<K,
|
|
10
|
-
protected createTopK(offset: number, limit: number, comparator: (a: TaggedValue<
|
|
9
|
+
export declare class TopKWithFractionalIndexBTreeOperator<K, T> extends TopKWithFractionalIndexOperator<K, T> {
|
|
10
|
+
protected createTopK(offset: number, limit: number, comparator: (a: TaggedValue<K, T>, b: TaggedValue<K, T>) => number): TopK<TaggedValue<K, T>>;
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
13
|
* Limits the number of results based on a comparator, with optional offset.
|
|
@@ -24,4 +24,4 @@ export declare class TopKWithFractionalIndexBTreeOperator<K, V1> extends TopKWit
|
|
|
24
24
|
* @param options - An optional object containing limit and offset properties
|
|
25
25
|
* @returns A piped operator that orders the elements and limits the number of results
|
|
26
26
|
*/
|
|
27
|
-
export declare function topKWithFractionalIndexBTree<KType
|
|
27
|
+
export declare function topKWithFractionalIndexBTree<KType, T>(comparator: (a: T, b: T) => number, options?: TopKWithFractionalIndexOptions): PipedOperator<[KType, T], [KType, IndexedValue<T>]>;
|
|
@@ -25,7 +25,7 @@ export declare function orderBy<T extends KeyValue<unknown, unknown>, Ve = unkno
|
|
|
25
25
|
* @returns A piped operator that orders the elements and limits the number of results
|
|
26
26
|
*/
|
|
27
27
|
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?: OrderByOptions<Ve>): (stream: IStreamBuilder<T>) => IStreamBuilder<
|
|
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?: OrderByOptions<Ve>): (stream: IStreamBuilder<T>) => IStreamBuilder<[T extends KeyValue<infer K, unknown> ? K : never, [T extends KeyValue<unknown, infer V> ? V : never, string]]>;
|
|
29
29
|
/**
|
|
30
30
|
* Orders the elements and limits the number of results, with optional offset and
|
|
31
31
|
* annotates the value with a fractional index.
|
|
@@ -35,4 +35,4 @@ export declare function orderByWithFractionalIndexBase<T extends KeyValue<unknow
|
|
|
35
35
|
* @param options - An optional object containing comparator, limit and offset properties
|
|
36
36
|
* @returns A piped operator that orders the elements and limits the number of results
|
|
37
37
|
*/
|
|
38
|
-
export declare function orderByWithFractionalIndex<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<
|
|
38
|
+
export declare function orderByWithFractionalIndex<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<[T extends KeyValue<infer K, unknown> ? K : never, [T extends KeyValue<unknown, infer V> ? V : never, string]]>;
|
|
@@ -75,26 +75,13 @@ function orderByWithFractionalIndexBase(topKFunction, valueExtractor, options) {
|
|
|
75
75
|
});
|
|
76
76
|
return (stream) => {
|
|
77
77
|
return stream.pipe(
|
|
78
|
-
|
|
79
|
-
(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
value
|
|
85
|
-
)
|
|
86
|
-
]
|
|
87
|
-
]
|
|
78
|
+
topKFunction(
|
|
79
|
+
(a, b) => comparator(valueExtractor(a), valueExtractor(b)),
|
|
80
|
+
{
|
|
81
|
+
limit,
|
|
82
|
+
offset
|
|
83
|
+
}
|
|
88
84
|
),
|
|
89
|
-
topKFunction((a, b) => comparator(a[1], b[1]), {
|
|
90
|
-
limit,
|
|
91
|
-
offset
|
|
92
|
-
}),
|
|
93
|
-
map(([_, [[key], index]]) => [key, index]),
|
|
94
|
-
innerJoin(stream),
|
|
95
|
-
map(([key, [index, value]]) => {
|
|
96
|
-
return [key, [value, index]];
|
|
97
|
-
}),
|
|
98
85
|
consolidate()
|
|
99
86
|
);
|
|
100
87
|
};
|
|
@@ -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?: 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
|
|
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?: OrderByOptions<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 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 }\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?: OrderByOptions<Ve>\n) {\n return orderByWithFractionalIndexBase(\n topKWithFractionalIndex,\n valueExtractor,\n options\n )\n}\n"],"names":[],"mappings":";;;;;AAsBO,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,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,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;"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { KeyValue } from '../types.js';
|
|
2
2
|
import { OrderByOptions } from './orderBy.js';
|
|
3
|
-
export declare function orderByWithFractionalIndexBTree<T extends KeyValue<unknown, unknown>, Ve = unknown>(valueExtractor: (value: T extends KeyValue<unknown, infer V> ? V : never) => Ve, options?: OrderByOptions<Ve>): (stream: import('../types.js').IStreamBuilder<T>) => import('../types.js').IStreamBuilder<
|
|
3
|
+
export declare function orderByWithFractionalIndexBTree<T extends KeyValue<unknown, unknown>, Ve = unknown>(valueExtractor: (value: T extends KeyValue<unknown, infer V> ? V : never) => Ve, options?: OrderByOptions<Ve>): (stream: import('../types.js').IStreamBuilder<T>) => import('../types.js').IStreamBuilder<[T extends KeyValue<infer K, unknown> ? K : never, [T extends KeyValue<unknown, infer V> ? V : never, string]]>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DifferenceStreamWriter, UnaryOperator, DifferenceStreamReader } from '../graph.js';
|
|
2
|
-
import {
|
|
2
|
+
import { PipedOperator } from '../types.js';
|
|
3
3
|
export interface TopKWithFractionalIndexOptions {
|
|
4
4
|
limit?: number;
|
|
5
5
|
offset?: number;
|
|
@@ -23,26 +23,23 @@ export interface TopK<V> {
|
|
|
23
23
|
* This operator maintains fractional indices for sorted elements
|
|
24
24
|
* and only updates indices when elements move position
|
|
25
25
|
*/
|
|
26
|
-
export declare class TopKWithFractionalIndexOperator<K,
|
|
26
|
+
export declare class TopKWithFractionalIndexOperator<K, T> extends UnaryOperator<[
|
|
27
27
|
K,
|
|
28
|
-
|
|
28
|
+
T
|
|
29
29
|
], [
|
|
30
30
|
K,
|
|
31
|
-
IndexedValue<
|
|
31
|
+
IndexedValue<T>
|
|
32
32
|
]> {
|
|
33
33
|
#private;
|
|
34
|
-
constructor(id: number, inputA: DifferenceStreamReader<[K,
|
|
35
|
-
protected createTopK(offset: number, limit: number, comparator: (a: TaggedValue<
|
|
34
|
+
constructor(id: number, inputA: DifferenceStreamReader<[K, T]>, output: DifferenceStreamWriter<[K, IndexedValue<T>]>, comparator: (a: T, b: T) => number, options: TopKWithFractionalIndexOptions);
|
|
35
|
+
protected createTopK(offset: number, limit: number, comparator: (a: TaggedValue<K, T>, b: TaggedValue<K, T>) => number): TopK<TaggedValue<K, T>>;
|
|
36
36
|
run(): void;
|
|
37
|
-
processElement(key: K, value:
|
|
37
|
+
processElement(key: K, value: T, multiplicity: number, result: Array<[[K, IndexedValue<T>], number]>): void;
|
|
38
|
+
private getMultiplicity;
|
|
39
|
+
private addKey;
|
|
38
40
|
}
|
|
39
41
|
/**
|
|
40
42
|
* Limits the number of results based on a comparator, with optional offset.
|
|
41
|
-
* This works on a keyed stream, where the key is the first element of the tuple.
|
|
42
|
-
* The ordering is within a key group, i.e. elements are sorted within a key group
|
|
43
|
-
* and the limit + offset is applied to that sorted group.
|
|
44
|
-
* To order the entire stream, key by the same value for all elements such as null.
|
|
45
|
-
*
|
|
46
43
|
* Uses fractional indexing to minimize the number of changes when elements move positions.
|
|
47
44
|
* Each element is assigned a fractional index that is lexicographically sortable.
|
|
48
45
|
* When elements move, only the indices of the moved elements are updated, not all elements.
|
|
@@ -51,11 +48,11 @@ export declare class TopKWithFractionalIndexOperator<K, V1> extends UnaryOperato
|
|
|
51
48
|
* @param options - An optional object containing limit and offset properties
|
|
52
49
|
* @returns A piped operator that orders the elements and limits the number of results
|
|
53
50
|
*/
|
|
54
|
-
export declare function topKWithFractionalIndex<KType
|
|
51
|
+
export declare function topKWithFractionalIndex<KType, T>(comparator: (a: T, b: T) => number, options?: TopKWithFractionalIndexOptions): PipedOperator<[KType, T], [KType, IndexedValue<T>]>;
|
|
55
52
|
export type FractionalIndex = string;
|
|
56
53
|
export type IndexedValue<V> = [V, FractionalIndex];
|
|
57
54
|
export declare function indexedValue<V>(value: V, index: FractionalIndex): IndexedValue<V>;
|
|
58
55
|
export declare function getValue<V>(indexedVal: IndexedValue<V>): V;
|
|
59
56
|
export declare function getIndex<V>(indexedVal: IndexedValue<V>): FractionalIndex;
|
|
60
57
|
export type Tag = number;
|
|
61
|
-
export type TaggedValue<V> = [V, Tag];
|
|
58
|
+
export type TaggedValue<K, V> = [K, V, Tag];
|
|
@@ -11,7 +11,6 @@ import { generateKeyBetween } from "fractional-indexing";
|
|
|
11
11
|
import { UnaryOperator, DifferenceStreamWriter } from "../graph.js";
|
|
12
12
|
import { StreamBuilder } from "../d2.js";
|
|
13
13
|
import { MultiSet } from "../multiset.js";
|
|
14
|
-
import { Index } from "../indexes.js";
|
|
15
14
|
import { binarySearch, globalObjectIdGenerator } from "../utils.js";
|
|
16
15
|
class TopKArray {
|
|
17
16
|
constructor(offset, limit, comparator) {
|
|
@@ -89,7 +88,8 @@ findIndex_fn = function(value) {
|
|
|
89
88
|
class TopKWithFractionalIndexOperator extends UnaryOperator {
|
|
90
89
|
constructor(id, inputA, output, comparator, options) {
|
|
91
90
|
super(id, inputA, output);
|
|
92
|
-
__privateAdd(this, _index, new
|
|
91
|
+
__privateAdd(this, _index, /* @__PURE__ */ new Map());
|
|
92
|
+
// maps keys to their multiplicity
|
|
93
93
|
/**
|
|
94
94
|
* topK data structure that supports insertions and deletions
|
|
95
95
|
* and returns changes to the topK.
|
|
@@ -98,7 +98,7 @@ class TopKWithFractionalIndexOperator extends UnaryOperator {
|
|
|
98
98
|
const limit = options.limit ?? Infinity;
|
|
99
99
|
const offset = options.offset ?? 0;
|
|
100
100
|
const compareTaggedValues = (a, b) => {
|
|
101
|
-
const valueComparison = comparator(
|
|
101
|
+
const valueComparison = comparator(getVal(a), getVal(b));
|
|
102
102
|
if (valueComparison !== 0) {
|
|
103
103
|
return valueComparison;
|
|
104
104
|
}
|
|
@@ -124,30 +124,47 @@ class TopKWithFractionalIndexOperator extends UnaryOperator {
|
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
processElement(key, value, multiplicity, result) {
|
|
127
|
-
const oldMultiplicity =
|
|
128
|
-
__privateGet(this, _index).addValue(key, [value, multiplicity]);
|
|
129
|
-
const newMultiplicity = __privateGet(this, _index).getMultiplicity(key, value);
|
|
127
|
+
const { oldMultiplicity, newMultiplicity } = this.addKey(key, multiplicity);
|
|
130
128
|
let res = {
|
|
131
129
|
moveIn: null,
|
|
132
130
|
moveOut: null
|
|
133
131
|
};
|
|
134
132
|
if (oldMultiplicity <= 0 && newMultiplicity > 0) {
|
|
135
|
-
const taggedValue = tagValue(value);
|
|
133
|
+
const taggedValue = tagValue(key, value);
|
|
136
134
|
res = __privateGet(this, _topK).insert(taggedValue);
|
|
137
135
|
} else if (oldMultiplicity > 0 && newMultiplicity <= 0) {
|
|
138
|
-
const taggedValue = tagValue(value);
|
|
136
|
+
const taggedValue = tagValue(key, value);
|
|
139
137
|
res = __privateGet(this, _topK).delete(taggedValue);
|
|
140
138
|
} else ;
|
|
141
139
|
if (res.moveIn) {
|
|
142
|
-
const
|
|
143
|
-
|
|
140
|
+
const index = getIndex(res.moveIn);
|
|
141
|
+
const taggedValue = getValue(res.moveIn);
|
|
142
|
+
const k = getKey(taggedValue);
|
|
143
|
+
const val = getVal(taggedValue);
|
|
144
|
+
result.push([[k, [val, index]], 1]);
|
|
144
145
|
}
|
|
145
146
|
if (res.moveOut) {
|
|
146
|
-
const
|
|
147
|
-
|
|
147
|
+
const index = getIndex(res.moveOut);
|
|
148
|
+
const taggedValue = getValue(res.moveOut);
|
|
149
|
+
const k = getKey(taggedValue);
|
|
150
|
+
const val = getVal(taggedValue);
|
|
151
|
+
result.push([[k, [val, index]], -1]);
|
|
148
152
|
}
|
|
149
153
|
return;
|
|
150
154
|
}
|
|
155
|
+
getMultiplicity(key) {
|
|
156
|
+
return __privateGet(this, _index).get(key) ?? 0;
|
|
157
|
+
}
|
|
158
|
+
addKey(key, multiplicity) {
|
|
159
|
+
const oldMultiplicity = this.getMultiplicity(key);
|
|
160
|
+
const newMultiplicity = oldMultiplicity + multiplicity;
|
|
161
|
+
if (newMultiplicity === 0) {
|
|
162
|
+
__privateGet(this, _index).delete(key);
|
|
163
|
+
} else {
|
|
164
|
+
__privateGet(this, _index).set(key, newMultiplicity);
|
|
165
|
+
}
|
|
166
|
+
return { oldMultiplicity, newMultiplicity };
|
|
167
|
+
}
|
|
151
168
|
}
|
|
152
169
|
_index = new WeakMap();
|
|
153
170
|
_topK = new WeakMap();
|
|
@@ -179,18 +196,18 @@ function getValue(indexedVal) {
|
|
|
179
196
|
function getIndex(indexedVal) {
|
|
180
197
|
return indexedVal[1];
|
|
181
198
|
}
|
|
182
|
-
function
|
|
183
|
-
return [
|
|
184
|
-
}
|
|
185
|
-
function tagValue(value) {
|
|
186
|
-
return [value, globalObjectIdGenerator.getId(value)];
|
|
199
|
+
function tagValue(key, value) {
|
|
200
|
+
return [key, value, globalObjectIdGenerator.getId(key)];
|
|
187
201
|
}
|
|
188
|
-
function
|
|
202
|
+
function getKey(tieBreakerTaggedValue) {
|
|
189
203
|
return tieBreakerTaggedValue[0];
|
|
190
204
|
}
|
|
191
|
-
function
|
|
205
|
+
function getVal(tieBreakerTaggedValue) {
|
|
192
206
|
return tieBreakerTaggedValue[1];
|
|
193
207
|
}
|
|
208
|
+
function getTag(tieBreakerTaggedValue) {
|
|
209
|
+
return tieBreakerTaggedValue[2];
|
|
210
|
+
}
|
|
194
211
|
export {
|
|
195
212
|
TopKWithFractionalIndexOperator,
|
|
196
213
|
getIndex,
|
|
@@ -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 { Index } from \"../indexes.js\"\nimport { binarySearch, globalObjectIdGenerator } from \"../utils.js\"\nimport type { DifferenceStreamReader } from \"../graph.js\"\nimport type { IStreamBuilder, KeyValue, 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, V1> extends UnaryOperator<\n [K, V1],\n [K, IndexedValue<V1>]\n> {\n #index = new Index<K, V1>()\n\n /**\n * topK data structure that supports insertions and deletions\n * and returns changes to the topK.\n */\n #topK: TopK<TaggedValue<V1>>\n\n constructor(\n id: number,\n inputA: DifferenceStreamReader<[K, V1]>,\n output: DifferenceStreamWriter<[K, [V1, string]]>,\n comparator: (a: V1, b: V1) => 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 = (a: TaggedValue<V1>, b: TaggedValue<V1>) => {\n // First compare on the value\n const valueComparison = comparator(untagValue(a), untagValue(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<V1>, b: TaggedValue<V1>) => number\n ): TopK<TaggedValue<V1>> {\n return new TopKArray(offset, limit, comparator)\n }\n\n run(): void {\n const result: Array<[[K, [V1, string]], 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: V1,\n multiplicity: number,\n result: Array<[[K, [V1, string]], number]>\n ): void {\n const oldMultiplicity = this.#index.getMultiplicity(key, value)\n this.#index.addValue(key, [value, multiplicity])\n const newMultiplicity = this.#index.getMultiplicity(key, value)\n\n let res: TopKChanges<TaggedValue<V1>> = {\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(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(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 valueWithoutTieBreaker = mapValue(res.moveIn, untagValue)\n result.push([[key, valueWithoutTieBreaker], 1])\n }\n\n if (res.moveOut) {\n const valueWithoutTieBreaker = mapValue(res.moveOut, untagValue)\n result.push([[key, valueWithoutTieBreaker], -1])\n }\n\n return\n }\n}\n\n/**\n * Limits the number of results based on a comparator, with optional offset.\n * This works on a keyed stream, where the key is the first element of the tuple.\n * The ordering is within a key group, i.e. elements are sorted within a key group\n * and the limit + offset is applied to that sorted group.\n * To order the entire stream, key by the same value for all elements such as null.\n *\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<\n KType extends T extends KeyValue<infer K, infer _V> ? K : never,\n V1Type extends T extends KeyValue<KType, infer V> ? V : never,\n T,\n>(\n comparator: (a: V1Type, b: V1Type) => number,\n options?: TopKWithFractionalIndexOptions\n): PipedOperator<T, KeyValue<KType, [V1Type, string]>> {\n const opts = options || {}\n\n return (\n stream: IStreamBuilder<T>\n ): IStreamBuilder<KeyValue<KType, [V1Type, string]>> => {\n const output = new StreamBuilder<KeyValue<KType, [V1Type, string]>>(\n stream.graph,\n new DifferenceStreamWriter<KeyValue<KType, [V1Type, string]>>()\n )\n const operator = new TopKWithFractionalIndexOperator<KType, V1Type>(\n stream.graph.getNextOperatorId(),\n stream.connectReader() as DifferenceStreamReader<KeyValue<KType, V1Type>>,\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\nfunction mapValue<V, W>(\n indexedVal: IndexedValue<V>,\n f: (value: V) => W\n): IndexedValue<W> {\n return [f(getValue(indexedVal)), getIndex(indexedVal)]\n}\n\nexport type Tag = number\nexport type TaggedValue<V> = [V, Tag]\n\nfunction tagValue<V>(value: V): TaggedValue<V> {\n return [value, globalObjectIdGenerator.getId(value)]\n}\n\nfunction untagValue<V>(tieBreakerTaggedValue: TaggedValue<V>): V {\n return tieBreakerTaggedValue[0]\n}\n\nfunction getTag<V>(tieBreakerTaggedValue: TaggedValue<V>): Tag {\n return tieBreakerTaggedValue[1]\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;AAoCA,MAAM,UAAgC;AAAA,EAMpC,YACE,QACA,OACA,YACA;AAVJ;AACE,sCAAwC,CAAA;AACxC;AACA;AACA;AAOE,uBAAK,YAAa;AAClB,uBAAK,UAAW,SAAS;AACzB,uBAAK,aAAc;AAAA,EACrB;AAAA,EAEA,OAAO,OAA0B;AAC/B,UAAM,SAAyB,EAAE,QAAQ,MAAM,SAAS,KAAA;AAGxD,UAAM,QAAQ,sBAAK,oCAAL,WAAgB;AAE9B,UAAM,cACJ,UAAU,IAAI,OAAO,SAAS,mBAAK,eAAc,QAAQ,CAAC,CAAE;AAC9D,UAAM,aACJ,UAAU,mBAAK,eAAc,SACzB,OACA,SAAS,mBAAK,eAAc,KAAK,CAAE;AACzC,UAAM,kBAAkB,mBAAmB,aAAa,UAAU;AAGlE,UAAM,MAAM,aAAa,OAAO,eAAe;AAE/C,uBAAK,eAAc,OAAO,OAAO,GAAG,GAAG;AAGvC,QAAI,QAAQ,mBAAK,WAAU;AAKzB,YAAM,cAAc,KAAK,IAAI,OAAO,mBAAK,WAAU;AACnD,UAAI,cAAc,mBAAK,eAAc,QAAQ;AAI3C,eAAO,SAAS,mBAAK,eAAc,WAAW;AAK9C,YAAI,mBAAK,YAAW,mBAAK,eAAc,QAAQ;AAC7C,iBAAO,UAAU,mBAAK,eAAc,mBAAK,SAAQ;AAAA,QACnD;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,OAAO,OAA0B;AAC/B,UAAM,SAAyB,EAAE,QAAQ,MAAM,SAAS,KAAA;AAGxD,UAAM,QAAQ,sBAAK,oCAAL,WAAgB;AAE9B,UAAM,CAAC,WAAW,IAAI,mBAAK,eAAc,OAAO,OAAO,CAAC;AAGxD,QAAI,QAAQ,mBAAK,WAAU;AAIzB,aAAO,UAAU;AACjB,UAAI,QAAQ,mBAAK,aAAY;AAM3B,cAAM,eAAe,mBAAK,cAAa;AACvC,YAAI,eAAe,mBAAK,eAAc,QAAQ;AAC5C,iBAAO,UAAU,mBAAK,eAAc,YAAY;AAAA,QAClD,OAAO;AAGL,iBAAO,UAAU;AAAA,QACnB;AAAA,MACF;AAKA,YAAM,cAAc,mBAAK,YAAW;AACpC,UAAI,cAAc,mBAAK,eAAc,QAAQ;AAC3C,eAAO,SAAS,mBAAK,eAAc,WAAW;AAAA,MAChD;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAWF;AApHE;AACA;AACA;AACA;AAJF;AAAA;AAAA;AAAA;AAgHE,wBAAW,OAAkB;AAC3B,SAAO;AAAA,IAAa,mBAAK;AAAA,IAAe,aAAa,OAAO,EAAE;AAAA,IAAG,CAAC,GAAG,MACnE,mBAAK,aAAL,WAAiB,SAAS,CAAC,GAAG,SAAS,CAAC;AAAA,EAAC;AAE7C;AAQK,MAAM,wCAA+C,cAG1D;AAAA,EASA,YACE,IACA,QACA,QACA,YACA,SACA;AACA,UAAM,IAAI,QAAQ,MAAM;AAf1B,+BAAS,IAAI,MAAA;AAMb;AAAA;AAAA;AAAA;AAAA;AAUE,UAAM,QAAQ,QAAQ,SAAS;AAC/B,UAAM,SAAS,QAAQ,UAAU;AACjC,UAAM,sBAAsB,CAAC,GAAoB,MAAuB;AAEtE,YAAM,kBAAkB,WAAW,WAAW,CAAC,GAAG,WAAW,CAAC,CAAC;AAC/D,UAAI,oBAAoB,GAAG;AACzB,eAAO;AAAA,MACT;AAEA,YAAM,cAAc,OAAO,CAAC;AAC5B,YAAM,cAAc,OAAO,CAAC;AAC5B,aAAO,cAAc;AAAA,IACvB;AACA,uBAAK,OAAQ,KAAK,WAAW,QAAQ,OAAO,mBAAmB;AAAA,EACjE;AAAA,EAEU,WACR,QACA,OACA,YACuB;AACvB,WAAO,IAAI,UAAU,QAAQ,OAAO,UAAU;AAAA,EAChD;AAAA,EAEA,MAAY;AACV,UAAM,SAA6C,CAAA;AACnD,eAAW,WAAW,KAAK,iBAAiB;AAC1C,iBAAW,CAAC,MAAM,YAAY,KAAK,QAAQ,YAAY;AACrD,cAAM,CAAC,KAAK,KAAK,IAAI;AACrB,aAAK,eAAe,KAAK,OAAO,cAAc,MAAM;AAAA,MACtD;AAAA,IACF;AAEA,QAAI,OAAO,SAAS,GAAG;AACrB,WAAK,OAAO,SAAS,IAAI,SAAS,MAAM,CAAC;AAAA,IAC3C;AAAA,EACF;AAAA,EAEA,eACE,KACA,OACA,cACA,QACM;AACN,UAAM,kBAAkB,mBAAK,QAAO,gBAAgB,KAAK,KAAK;AAC9D,uBAAK,QAAO,SAAS,KAAK,CAAC,OAAO,YAAY,CAAC;AAC/C,UAAM,kBAAkB,mBAAK,QAAO,gBAAgB,KAAK,KAAK;AAE9D,QAAI,MAAoC;AAAA,MACtC,QAAQ;AAAA,MACR,SAAS;AAAA,IAAA;AAEX,QAAI,mBAAmB,KAAK,kBAAkB,GAAG;AAG/C,YAAM,cAAc,SAAS,KAAK;AAClC,YAAM,mBAAK,OAAM,OAAO,WAAW;AAAA,IACrC,WAAW,kBAAkB,KAAK,mBAAmB,GAAG;AAGtD,YAAM,cAAc,SAAS,KAAK;AAClC,YAAM,mBAAK,OAAM,OAAO,WAAW;AAAA,IACrC,MAAO;AAMP,QAAI,IAAI,QAAQ;AACd,YAAM,yBAAyB,SAAS,IAAI,QAAQ,UAAU;AAC9D,aAAO,KAAK,CAAC,CAAC,KAAK,sBAAsB,GAAG,CAAC,CAAC;AAAA,IAChD;AAEA,QAAI,IAAI,SAAS;AACf,YAAM,yBAAyB,SAAS,IAAI,SAAS,UAAU;AAC/D,aAAO,KAAK,CAAC,CAAC,KAAK,sBAAsB,GAAG,EAAE,CAAC;AAAA,IACjD;AAEA;AAAA,EACF;AACF;AAhGE;AAMA;AA2GK,SAAS,wBAKd,YACA,SACqD;AACrD,QAAM,OAAO,WAAW,CAAA;AAExB,SAAO,CACL,WACsD;AACtD,UAAM,SAAS,IAAI;AAAA,MACjB,OAAO;AAAA,MACP,IAAI,uBAAA;AAAA,IAA0D;AAEhE,UAAM,WAAW,IAAI;AAAA,MACnB,OAAO,MAAM,kBAAA;AAAA,MACb,OAAO,cAAA;AAAA,MACP,OAAO;AAAA,MACP;AAAA,MACA;AAAA,IAAA;AAEF,WAAO,MAAM,YAAY,QAAQ;AACjC,WAAO,MAAM,UAAU,OAAO,cAAA,CAAe;AAC7C,WAAO;AAAA,EACT;AACF;AAMO,SAAS,aACd,OACA,OACiB;AACjB,SAAO,CAAC,OAAO,KAAK;AACtB;AAEO,SAAS,SAAY,YAAgC;AAC1D,SAAO,WAAW,CAAC;AACrB;AAEO,SAAS,SAAY,YAA8C;AACxE,SAAO,WAAW,CAAC;AACrB;AAEA,SAAS,SACP,YACA,GACiB;AACjB,SAAO,CAAC,EAAE,SAAS,UAAU,CAAC,GAAG,SAAS,UAAU,CAAC;AACvD;AAKA,SAAS,SAAY,OAA0B;AAC7C,SAAO,CAAC,OAAO,wBAAwB,MAAM,KAAK,CAAC;AACrD;AAEA,SAAS,WAAc,uBAA0C;AAC/D,SAAO,sBAAsB,CAAC;AAChC;AAEA,SAAS,OAAU,uBAA4C;AAC7D,SAAO,sBAAsB,CAAC;AAChC;"}
|
|
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,13 +1,13 @@
|
|
|
1
|
-
import { TopKWithFractionalIndexOperator, TaggedValue, TopK, TopKWithFractionalIndexOptions } from './topKWithFractionalIndex.js';
|
|
2
|
-
import {
|
|
1
|
+
import { TopKWithFractionalIndexOperator, IndexedValue, TaggedValue, TopK, TopKWithFractionalIndexOptions } from './topKWithFractionalIndex.js';
|
|
2
|
+
import { PipedOperator } from '../types.js';
|
|
3
3
|
export declare function loadBTree(): Promise<void>;
|
|
4
4
|
/**
|
|
5
5
|
* Operator for fractional indexed topK operations
|
|
6
6
|
* This operator maintains fractional indices for sorted elements
|
|
7
7
|
* and only updates indices when elements move position
|
|
8
8
|
*/
|
|
9
|
-
export declare class TopKWithFractionalIndexBTreeOperator<K,
|
|
10
|
-
protected createTopK(offset: number, limit: number, comparator: (a: TaggedValue<
|
|
9
|
+
export declare class TopKWithFractionalIndexBTreeOperator<K, T> extends TopKWithFractionalIndexOperator<K, T> {
|
|
10
|
+
protected createTopK(offset: number, limit: number, comparator: (a: TaggedValue<K, T>, b: TaggedValue<K, T>) => number): TopK<TaggedValue<K, T>>;
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
13
|
* Limits the number of results based on a comparator, with optional offset.
|
|
@@ -24,4 +24,4 @@ export declare class TopKWithFractionalIndexBTreeOperator<K, V1> extends TopKWit
|
|
|
24
24
|
* @param options - An optional object containing limit and offset properties
|
|
25
25
|
* @returns A piped operator that orders the elements and limits the number of results
|
|
26
26
|
*/
|
|
27
|
-
export declare function topKWithFractionalIndexBTree<KType
|
|
27
|
+
export declare function topKWithFractionalIndexBTree<KType, T>(comparator: (a: T, b: T) => number, options?: TopKWithFractionalIndexOptions): PipedOperator<[KType, T], [KType, IndexedValue<T>]>;
|
package/package.json
CHANGED
package/src/operators/orderBy.ts
CHANGED
|
@@ -138,6 +138,9 @@ export function orderByWithFractionalIndexBase<
|
|
|
138
138
|
) => Ve,
|
|
139
139
|
options?: OrderByOptions<Ve>
|
|
140
140
|
) {
|
|
141
|
+
type KeyType = T extends KeyValue<infer K, unknown> ? K : never
|
|
142
|
+
type ValueType = T extends KeyValue<unknown, infer V> ? V : never
|
|
143
|
+
|
|
141
144
|
const limit = options?.limit ?? Infinity
|
|
142
145
|
const offset = options?.offset ?? 0
|
|
143
146
|
const comparator =
|
|
@@ -151,37 +154,16 @@ export function orderByWithFractionalIndexBase<
|
|
|
151
154
|
|
|
152
155
|
return (
|
|
153
156
|
stream: IStreamBuilder<T>
|
|
154
|
-
): IStreamBuilder<
|
|
155
|
-
KeyValue<
|
|
156
|
-
T extends KeyValue<infer K, unknown> ? K : never,
|
|
157
|
-
[T extends KeyValue<unknown, infer V> ? V : never, string]
|
|
158
|
-
>
|
|
159
|
-
> => {
|
|
160
|
-
type KeyType = T extends KeyValue<infer K, unknown> ? K : never
|
|
161
|
-
type ValueType = T extends KeyValue<unknown, infer V> ? V : never
|
|
162
|
-
|
|
157
|
+
): IStreamBuilder<[KeyType, [ValueType, string]]> => {
|
|
163
158
|
return stream.pipe(
|
|
164
|
-
|
|
165
|
-
(
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
value as T extends KeyValue<unknown, infer V> ? V : never
|
|
172
|
-
),
|
|
173
|
-
],
|
|
174
|
-
] as KeyValue<null, [KeyType, Ve]>
|
|
159
|
+
topKFunction(
|
|
160
|
+
(a: ValueType, b: ValueType) =>
|
|
161
|
+
comparator(valueExtractor(a), valueExtractor(b)),
|
|
162
|
+
{
|
|
163
|
+
limit,
|
|
164
|
+
offset,
|
|
165
|
+
}
|
|
175
166
|
),
|
|
176
|
-
topKFunction((a, b) => comparator(a[1], b[1]), {
|
|
177
|
-
limit,
|
|
178
|
-
offset,
|
|
179
|
-
}),
|
|
180
|
-
map(([_, [[key], index]]) => [key, index] as KeyValue<KeyType, string>),
|
|
181
|
-
innerJoin(stream),
|
|
182
|
-
map(([key, [index, value]]) => {
|
|
183
|
-
return [key, [value, index]] as KeyValue<KeyType, [ValueType, string]>
|
|
184
|
-
}),
|
|
185
167
|
consolidate()
|
|
186
168
|
)
|
|
187
169
|
}
|
|
@@ -2,10 +2,9 @@ import { generateKeyBetween } from "fractional-indexing"
|
|
|
2
2
|
import { DifferenceStreamWriter, UnaryOperator } from "../graph.js"
|
|
3
3
|
import { StreamBuilder } from "../d2.js"
|
|
4
4
|
import { MultiSet } from "../multiset.js"
|
|
5
|
-
import { Index } from "../indexes.js"
|
|
6
5
|
import { binarySearch, globalObjectIdGenerator } from "../utils.js"
|
|
7
6
|
import type { DifferenceStreamReader } from "../graph.js"
|
|
8
|
-
import type { IStreamBuilder,
|
|
7
|
+
import type { IStreamBuilder, PipedOperator } from "../types.js"
|
|
9
8
|
|
|
10
9
|
export interface TopKWithFractionalIndexOptions {
|
|
11
10
|
limit?: number
|
|
@@ -158,31 +157,34 @@ class TopKArray<V> implements TopK<V> {
|
|
|
158
157
|
* This operator maintains fractional indices for sorted elements
|
|
159
158
|
* and only updates indices when elements move position
|
|
160
159
|
*/
|
|
161
|
-
export class TopKWithFractionalIndexOperator<K,
|
|
162
|
-
[K,
|
|
163
|
-
[K, IndexedValue<
|
|
160
|
+
export class TopKWithFractionalIndexOperator<K, T> extends UnaryOperator<
|
|
161
|
+
[K, T],
|
|
162
|
+
[K, IndexedValue<T>]
|
|
164
163
|
> {
|
|
165
|
-
#index
|
|
164
|
+
#index: Map<K, number> = new Map() // maps keys to their multiplicity
|
|
166
165
|
|
|
167
166
|
/**
|
|
168
167
|
* topK data structure that supports insertions and deletions
|
|
169
168
|
* and returns changes to the topK.
|
|
170
169
|
*/
|
|
171
|
-
#topK: TopK<TaggedValue<
|
|
170
|
+
#topK: TopK<TaggedValue<K, T>>
|
|
172
171
|
|
|
173
172
|
constructor(
|
|
174
173
|
id: number,
|
|
175
|
-
inputA: DifferenceStreamReader<[K,
|
|
176
|
-
output: DifferenceStreamWriter<[K,
|
|
177
|
-
comparator: (a:
|
|
174
|
+
inputA: DifferenceStreamReader<[K, T]>,
|
|
175
|
+
output: DifferenceStreamWriter<[K, IndexedValue<T>]>,
|
|
176
|
+
comparator: (a: T, b: T) => number,
|
|
178
177
|
options: TopKWithFractionalIndexOptions
|
|
179
178
|
) {
|
|
180
179
|
super(id, inputA, output)
|
|
181
180
|
const limit = options.limit ?? Infinity
|
|
182
181
|
const offset = options.offset ?? 0
|
|
183
|
-
const compareTaggedValues = (
|
|
182
|
+
const compareTaggedValues = (
|
|
183
|
+
a: TaggedValue<K, T>,
|
|
184
|
+
b: TaggedValue<K, T>
|
|
185
|
+
) => {
|
|
184
186
|
// First compare on the value
|
|
185
|
-
const valueComparison = comparator(
|
|
187
|
+
const valueComparison = comparator(getVal(a), getVal(b))
|
|
186
188
|
if (valueComparison !== 0) {
|
|
187
189
|
return valueComparison
|
|
188
190
|
}
|
|
@@ -197,13 +199,13 @@ export class TopKWithFractionalIndexOperator<K, V1> extends UnaryOperator<
|
|
|
197
199
|
protected createTopK(
|
|
198
200
|
offset: number,
|
|
199
201
|
limit: number,
|
|
200
|
-
comparator: (a: TaggedValue<
|
|
201
|
-
): TopK<TaggedValue<
|
|
202
|
+
comparator: (a: TaggedValue<K, T>, b: TaggedValue<K, T>) => number
|
|
203
|
+
): TopK<TaggedValue<K, T>> {
|
|
202
204
|
return new TopKArray(offset, limit, comparator)
|
|
203
205
|
}
|
|
204
206
|
|
|
205
207
|
run(): void {
|
|
206
|
-
const result: Array<[[K,
|
|
208
|
+
const result: Array<[[K, IndexedValue<T>], number]> = []
|
|
207
209
|
for (const message of this.inputMessages()) {
|
|
208
210
|
for (const [item, multiplicity] of message.getInner()) {
|
|
209
211
|
const [key, value] = item
|
|
@@ -218,27 +220,25 @@ export class TopKWithFractionalIndexOperator<K, V1> extends UnaryOperator<
|
|
|
218
220
|
|
|
219
221
|
processElement(
|
|
220
222
|
key: K,
|
|
221
|
-
value:
|
|
223
|
+
value: T,
|
|
222
224
|
multiplicity: number,
|
|
223
|
-
result: Array<[[K,
|
|
225
|
+
result: Array<[[K, IndexedValue<T>], number]>
|
|
224
226
|
): void {
|
|
225
|
-
const oldMultiplicity = this
|
|
226
|
-
this.#index.addValue(key, [value, multiplicity])
|
|
227
|
-
const newMultiplicity = this.#index.getMultiplicity(key, value)
|
|
227
|
+
const { oldMultiplicity, newMultiplicity } = this.addKey(key, multiplicity)
|
|
228
228
|
|
|
229
|
-
let res: TopKChanges<TaggedValue<
|
|
229
|
+
let res: TopKChanges<TaggedValue<K, T>> = {
|
|
230
230
|
moveIn: null,
|
|
231
231
|
moveOut: null,
|
|
232
232
|
}
|
|
233
233
|
if (oldMultiplicity <= 0 && newMultiplicity > 0) {
|
|
234
234
|
// The value was invisible but should now be visible
|
|
235
235
|
// Need to insert it into the array of sorted values
|
|
236
|
-
const taggedValue = tagValue(value)
|
|
236
|
+
const taggedValue = tagValue(key, value)
|
|
237
237
|
res = this.#topK.insert(taggedValue)
|
|
238
238
|
} else if (oldMultiplicity > 0 && newMultiplicity <= 0) {
|
|
239
239
|
// The value was visible but should now be invisible
|
|
240
240
|
// Need to remove it from the array of sorted values
|
|
241
|
-
const taggedValue = tagValue(value)
|
|
241
|
+
const taggedValue = tagValue(key, value)
|
|
242
242
|
res = this.#topK.delete(taggedValue)
|
|
243
243
|
} else {
|
|
244
244
|
// The value was invisible and it remains invisible
|
|
@@ -247,26 +247,45 @@ export class TopKWithFractionalIndexOperator<K, V1> extends UnaryOperator<
|
|
|
247
247
|
}
|
|
248
248
|
|
|
249
249
|
if (res.moveIn) {
|
|
250
|
-
const
|
|
251
|
-
|
|
250
|
+
const index = getIndex(res.moveIn)
|
|
251
|
+
const taggedValue = getValue(res.moveIn)
|
|
252
|
+
const k = getKey(taggedValue)
|
|
253
|
+
const val = getVal(taggedValue)
|
|
254
|
+
result.push([[k, [val, index]], 1])
|
|
252
255
|
}
|
|
253
256
|
|
|
254
257
|
if (res.moveOut) {
|
|
255
|
-
const
|
|
256
|
-
|
|
258
|
+
const index = getIndex(res.moveOut)
|
|
259
|
+
const taggedValue = getValue(res.moveOut)
|
|
260
|
+
const k = getKey(taggedValue)
|
|
261
|
+
const val = getVal(taggedValue)
|
|
262
|
+
result.push([[k, [val, index]], -1])
|
|
257
263
|
}
|
|
258
264
|
|
|
259
265
|
return
|
|
260
266
|
}
|
|
267
|
+
|
|
268
|
+
private getMultiplicity(key: K): number {
|
|
269
|
+
return this.#index.get(key) ?? 0
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private addKey(
|
|
273
|
+
key: K,
|
|
274
|
+
multiplicity: number
|
|
275
|
+
): { oldMultiplicity: number; newMultiplicity: number } {
|
|
276
|
+
const oldMultiplicity = this.getMultiplicity(key)
|
|
277
|
+
const newMultiplicity = oldMultiplicity + multiplicity
|
|
278
|
+
if (newMultiplicity === 0) {
|
|
279
|
+
this.#index.delete(key)
|
|
280
|
+
} else {
|
|
281
|
+
this.#index.set(key, newMultiplicity)
|
|
282
|
+
}
|
|
283
|
+
return { oldMultiplicity, newMultiplicity }
|
|
284
|
+
}
|
|
261
285
|
}
|
|
262
286
|
|
|
263
287
|
/**
|
|
264
288
|
* Limits the number of results based on a comparator, with optional offset.
|
|
265
|
-
* This works on a keyed stream, where the key is the first element of the tuple.
|
|
266
|
-
* The ordering is within a key group, i.e. elements are sorted within a key group
|
|
267
|
-
* and the limit + offset is applied to that sorted group.
|
|
268
|
-
* To order the entire stream, key by the same value for all elements such as null.
|
|
269
|
-
*
|
|
270
289
|
* Uses fractional indexing to minimize the number of changes when elements move positions.
|
|
271
290
|
* Each element is assigned a fractional index that is lexicographically sortable.
|
|
272
291
|
* When elements move, only the indices of the moved elements are updated, not all elements.
|
|
@@ -275,26 +294,22 @@ export class TopKWithFractionalIndexOperator<K, V1> extends UnaryOperator<
|
|
|
275
294
|
* @param options - An optional object containing limit and offset properties
|
|
276
295
|
* @returns A piped operator that orders the elements and limits the number of results
|
|
277
296
|
*/
|
|
278
|
-
export function topKWithFractionalIndex<
|
|
279
|
-
|
|
280
|
-
V1Type extends T extends KeyValue<KType, infer V> ? V : never,
|
|
281
|
-
T,
|
|
282
|
-
>(
|
|
283
|
-
comparator: (a: V1Type, b: V1Type) => number,
|
|
297
|
+
export function topKWithFractionalIndex<KType, T>(
|
|
298
|
+
comparator: (a: T, b: T) => number,
|
|
284
299
|
options?: TopKWithFractionalIndexOptions
|
|
285
|
-
): PipedOperator<
|
|
300
|
+
): PipedOperator<[KType, T], [KType, IndexedValue<T>]> {
|
|
286
301
|
const opts = options || {}
|
|
287
302
|
|
|
288
303
|
return (
|
|
289
|
-
stream: IStreamBuilder<T>
|
|
290
|
-
): IStreamBuilder<
|
|
291
|
-
const output = new StreamBuilder<
|
|
304
|
+
stream: IStreamBuilder<[KType, T]>
|
|
305
|
+
): IStreamBuilder<[KType, IndexedValue<T>]> => {
|
|
306
|
+
const output = new StreamBuilder<[KType, IndexedValue<T>]>(
|
|
292
307
|
stream.graph,
|
|
293
|
-
new DifferenceStreamWriter<
|
|
308
|
+
new DifferenceStreamWriter<[KType, IndexedValue<T>]>()
|
|
294
309
|
)
|
|
295
|
-
const operator = new TopKWithFractionalIndexOperator<KType,
|
|
310
|
+
const operator = new TopKWithFractionalIndexOperator<KType, T>(
|
|
296
311
|
stream.graph.getNextOperatorId(),
|
|
297
|
-
stream.connectReader()
|
|
312
|
+
stream.connectReader(),
|
|
298
313
|
output.writer,
|
|
299
314
|
comparator,
|
|
300
315
|
opts
|
|
@@ -324,24 +339,21 @@ export function getIndex<V>(indexedVal: IndexedValue<V>): FractionalIndex {
|
|
|
324
339
|
return indexedVal[1]
|
|
325
340
|
}
|
|
326
341
|
|
|
327
|
-
function mapValue<V, W>(
|
|
328
|
-
indexedVal: IndexedValue<V>,
|
|
329
|
-
f: (value: V) => W
|
|
330
|
-
): IndexedValue<W> {
|
|
331
|
-
return [f(getValue(indexedVal)), getIndex(indexedVal)]
|
|
332
|
-
}
|
|
333
|
-
|
|
334
342
|
export type Tag = number
|
|
335
|
-
export type TaggedValue<V> = [V, Tag]
|
|
343
|
+
export type TaggedValue<K, V> = [K, V, Tag]
|
|
336
344
|
|
|
337
|
-
function tagValue<V>(value: V): TaggedValue<V> {
|
|
338
|
-
return [value, globalObjectIdGenerator.getId(
|
|
345
|
+
function tagValue<K, V>(key: K, value: V): TaggedValue<K, V> {
|
|
346
|
+
return [key, value, globalObjectIdGenerator.getId(key)]
|
|
339
347
|
}
|
|
340
348
|
|
|
341
|
-
function
|
|
349
|
+
function getKey<K, V>(tieBreakerTaggedValue: TaggedValue<K, V>): K {
|
|
342
350
|
return tieBreakerTaggedValue[0]
|
|
343
351
|
}
|
|
344
352
|
|
|
345
|
-
function
|
|
353
|
+
function getVal<K, V>(tieBreakerTaggedValue: TaggedValue<K, V>): V {
|
|
346
354
|
return tieBreakerTaggedValue[1]
|
|
347
355
|
}
|
|
356
|
+
|
|
357
|
+
function getTag<K, V>(tieBreakerTaggedValue: TaggedValue<K, V>): Tag {
|
|
358
|
+
return tieBreakerTaggedValue[2]
|
|
359
|
+
}
|
|
@@ -7,8 +7,7 @@ import {
|
|
|
7
7
|
getValue,
|
|
8
8
|
indexedValue,
|
|
9
9
|
} from "./topKWithFractionalIndex.js"
|
|
10
|
-
import type { IStreamBuilder,
|
|
11
|
-
import type { DifferenceStreamReader } from "../graph.js"
|
|
10
|
+
import type { IStreamBuilder, PipedOperator } from "../types.js"
|
|
12
11
|
import type {
|
|
13
12
|
IndexedValue,
|
|
14
13
|
TaggedValue,
|
|
@@ -238,13 +237,13 @@ class TopKTree<V> implements TopK<V> {
|
|
|
238
237
|
*/
|
|
239
238
|
export class TopKWithFractionalIndexBTreeOperator<
|
|
240
239
|
K,
|
|
241
|
-
|
|
242
|
-
> extends TopKWithFractionalIndexOperator<K,
|
|
240
|
+
T,
|
|
241
|
+
> extends TopKWithFractionalIndexOperator<K, T> {
|
|
243
242
|
protected override createTopK(
|
|
244
243
|
offset: number,
|
|
245
244
|
limit: number,
|
|
246
|
-
comparator: (a: TaggedValue<
|
|
247
|
-
): TopK<TaggedValue<
|
|
245
|
+
comparator: (a: TaggedValue<K, T>, b: TaggedValue<K, T>) => number
|
|
246
|
+
): TopK<TaggedValue<K, T>> {
|
|
248
247
|
if (BTree === undefined) {
|
|
249
248
|
throw new Error(
|
|
250
249
|
`B+ tree not loaded. You need to call loadBTree() before using TopKWithFractionalIndexBTreeOperator.`
|
|
@@ -269,14 +268,10 @@ export class TopKWithFractionalIndexBTreeOperator<
|
|
|
269
268
|
* @param options - An optional object containing limit and offset properties
|
|
270
269
|
* @returns A piped operator that orders the elements and limits the number of results
|
|
271
270
|
*/
|
|
272
|
-
export function topKWithFractionalIndexBTree<
|
|
273
|
-
|
|
274
|
-
V1Type extends T extends KeyValue<KType, infer V> ? V : never,
|
|
275
|
-
T,
|
|
276
|
-
>(
|
|
277
|
-
comparator: (a: V1Type, b: V1Type) => number,
|
|
271
|
+
export function topKWithFractionalIndexBTree<KType, T>(
|
|
272
|
+
comparator: (a: T, b: T) => number,
|
|
278
273
|
options?: TopKWithFractionalIndexOptions
|
|
279
|
-
): PipedOperator<
|
|
274
|
+
): PipedOperator<[KType, T], [KType, IndexedValue<T>]> {
|
|
280
275
|
const opts = options || {}
|
|
281
276
|
|
|
282
277
|
if (BTree === undefined) {
|
|
@@ -286,15 +281,15 @@ export function topKWithFractionalIndexBTree<
|
|
|
286
281
|
}
|
|
287
282
|
|
|
288
283
|
return (
|
|
289
|
-
stream: IStreamBuilder<T>
|
|
290
|
-
): IStreamBuilder<
|
|
291
|
-
const output = new StreamBuilder<
|
|
284
|
+
stream: IStreamBuilder<[KType, T]>
|
|
285
|
+
): IStreamBuilder<[KType, IndexedValue<T>]> => {
|
|
286
|
+
const output = new StreamBuilder<[KType, IndexedValue<T>]>(
|
|
292
287
|
stream.graph,
|
|
293
|
-
new DifferenceStreamWriter<
|
|
288
|
+
new DifferenceStreamWriter<[KType, IndexedValue<T>]>()
|
|
294
289
|
)
|
|
295
|
-
const operator = new
|
|
290
|
+
const operator = new TopKWithFractionalIndexBTreeOperator<KType, T>(
|
|
296
291
|
stream.graph.getNextOperatorId(),
|
|
297
|
-
stream.connectReader()
|
|
292
|
+
stream.connectReader(),
|
|
298
293
|
output.writer,
|
|
299
294
|
comparator,
|
|
300
295
|
opts
|