@tanstack/db-ivm 0.1.2 → 0.1.4
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/hashIndex.cjs +83 -0
- package/dist/cjs/hashIndex.cjs.map +1 -0
- package/dist/cjs/hashIndex.d.cts +25 -0
- package/dist/cjs/hashing/hash.cjs +122 -0
- package/dist/cjs/hashing/hash.cjs.map +1 -0
- package/dist/cjs/hashing/hash.d.cts +1 -0
- package/dist/cjs/hashing/index.d.cts +2 -0
- package/dist/cjs/hashing/murmur.cjs +103 -0
- package/dist/cjs/hashing/murmur.cjs.map +1 -0
- package/dist/cjs/hashing/murmur.d.cts +26 -0
- package/dist/cjs/indexes.cjs +97 -45
- package/dist/cjs/indexes.cjs.map +1 -1
- package/dist/cjs/indexes.d.cts +0 -2
- package/dist/cjs/multiset.cjs +2 -1
- package/dist/cjs/multiset.cjs.map +1 -1
- package/dist/cjs/operators/distinct.cjs +2 -2
- package/dist/cjs/operators/distinct.cjs.map +1 -1
- package/dist/cjs/operators/groupBy.cjs +5 -4
- package/dist/cjs/operators/groupBy.cjs.map +1 -1
- package/dist/cjs/operators/groupBy.d.cts +1 -1
- package/dist/cjs/utils.cjs +13 -33
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +7 -4
- package/dist/cjs/valueIndex.cjs +74 -0
- package/dist/cjs/valueIndex.cjs.map +1 -0
- package/dist/cjs/valueIndex.d.cts +17 -0
- package/dist/esm/hashIndex.d.ts +25 -0
- package/dist/esm/hashIndex.js +83 -0
- package/dist/esm/hashIndex.js.map +1 -0
- package/dist/esm/hashing/hash.d.ts +1 -0
- package/dist/esm/hashing/hash.js +122 -0
- package/dist/esm/hashing/hash.js.map +1 -0
- package/dist/esm/hashing/index.d.ts +2 -0
- package/dist/esm/hashing/murmur.d.ts +26 -0
- package/dist/esm/hashing/murmur.js +103 -0
- package/dist/esm/hashing/murmur.js.map +1 -0
- package/dist/esm/indexes.d.ts +0 -2
- package/dist/esm/indexes.js +98 -46
- package/dist/esm/indexes.js.map +1 -1
- package/dist/esm/multiset.js +2 -1
- package/dist/esm/multiset.js.map +1 -1
- package/dist/esm/operators/distinct.js +1 -1
- package/dist/esm/operators/distinct.js.map +1 -1
- package/dist/esm/operators/groupBy.d.ts +1 -1
- package/dist/esm/operators/groupBy.js +5 -4
- package/dist/esm/operators/groupBy.js.map +1 -1
- package/dist/esm/utils.d.ts +7 -4
- package/dist/esm/utils.js +13 -33
- package/dist/esm/utils.js.map +1 -1
- package/dist/esm/valueIndex.d.ts +17 -0
- package/dist/esm/valueIndex.js +74 -0
- package/dist/esm/valueIndex.js.map +1 -0
- package/package.json +1 -3
- package/src/hashIndex.ts +94 -0
- package/src/hashing/hash.ts +157 -0
- package/src/hashing/index.ts +2 -0
- package/src/hashing/murmur.ts +128 -0
- package/src/indexes.ts +106 -47
- package/src/multiset.ts +1 -1
- package/src/operators/distinct.ts +4 -4
- package/src/operators/groupBy.ts +7 -4
- package/src/utils.ts +31 -48
- package/src/valueIndex.ts +78 -0
package/dist/cjs/indexes.d.cts
CHANGED
|
@@ -10,8 +10,6 @@ export declare class Index<K, V> {
|
|
|
10
10
|
toString(indent?: boolean): string;
|
|
11
11
|
get(key: K): Array<[V, number]>;
|
|
12
12
|
getMultiplicity(key: K, value: V): number;
|
|
13
|
-
entries(): MapIterator<[K, Map<V, number>]>;
|
|
14
|
-
keys(): MapIterator<K>;
|
|
15
13
|
has(key: K): boolean;
|
|
16
14
|
get size(): number;
|
|
17
15
|
addValue(key: K, value: [V, number]): void;
|
package/dist/cjs/multiset.cjs
CHANGED
|
@@ -10,6 +10,7 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
|
|
|
10
10
|
var _inner, _MultiSet_instances, consolidateKeyed_fn, consolidateUnkeyed_fn;
|
|
11
11
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
12
12
|
const utils = require("./utils.cjs");
|
|
13
|
+
const hash = require("./hashing/hash.cjs");
|
|
13
14
|
const _MultiSet = class _MultiSet {
|
|
14
15
|
constructor(data = []) {
|
|
15
16
|
__privateAdd(this, _MultiSet_instances);
|
|
@@ -153,7 +154,7 @@ consolidateUnkeyed_fn = function() {
|
|
|
153
154
|
}
|
|
154
155
|
const requireJson = hasOther || hasString && hasNumber;
|
|
155
156
|
for (const [data, multiplicity] of __privateGet(this, _inner)) {
|
|
156
|
-
const key = requireJson ?
|
|
157
|
+
const key = requireJson ? hash.hash(data) : data;
|
|
157
158
|
if (requireJson && !values.has(key)) {
|
|
158
159
|
values.set(key, data);
|
|
159
160
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"multiset.cjs","sources":["../../src/multiset.ts"],"sourcesContent":["import {\n DefaultMap,\n chunkedArrayPush,\n globalObjectIdGenerator,\n hash,\n} from \"./utils.js\"\n\nexport type MultiSetArray<T> = Array<[T, number]>\nexport type KeyedData<T> = [key: string, value: T]\n\n/**\n * A multiset of data.\n */\nexport class MultiSet<T> {\n #inner: MultiSetArray<T>\n\n constructor(data: MultiSetArray<T> = []) {\n this.#inner = data\n }\n\n toString(indent = false): string {\n return `MultiSet(${JSON.stringify(this.#inner, null, indent ? 2 : undefined)})`\n }\n\n toJSON(): string {\n return JSON.stringify(Array.from(this.getInner()))\n }\n\n static fromJSON<U>(json: string): MultiSet<U> {\n return new MultiSet(JSON.parse(json))\n }\n\n /**\n * Apply a function to all records in the collection.\n */\n map<U>(f: (data: T) => U): MultiSet<U> {\n return new MultiSet(\n this.#inner.map(([data, multiplicity]) => [f(data), multiplicity])\n )\n }\n\n /**\n * Filter out records for which a function f(record) evaluates to False.\n */\n filter(f: (data: T) => boolean): MultiSet<T> {\n return new MultiSet(this.#inner.filter(([data, _]) => f(data)))\n }\n\n /**\n * Negate all multiplicities in the collection.\n */\n negate(): MultiSet<T> {\n return new MultiSet(\n this.#inner.map(([data, multiplicity]) => [data, -multiplicity])\n )\n }\n\n /**\n * Concatenate two collections together.\n */\n concat(other: MultiSet<T>): MultiSet<T> {\n const out: MultiSetArray<T> = []\n chunkedArrayPush(out, this.#inner)\n chunkedArrayPush(out, other.getInner())\n return new MultiSet(out)\n }\n\n /**\n * Produce as output a collection that is logically equivalent to the input\n * but which combines identical instances of the same record into one\n * (record, multiplicity) pair.\n */\n consolidate(): MultiSet<T> {\n // Check if this looks like a keyed multiset (first item is a tuple of length 2)\n if (this.#inner.length > 0) {\n const firstItem = this.#inner[0]?.[0]\n if (Array.isArray(firstItem) && firstItem.length === 2) {\n return this.#consolidateKeyed()\n }\n }\n\n // Fall back to original method for unkeyed data\n return this.#consolidateUnkeyed()\n }\n\n /**\n * Private method for consolidating keyed multisets where keys are strings/numbers\n * and values are compared by reference equality.\n *\n * This method provides significant performance improvements over the hash-based approach\n * by using WeakMap for object reference tracking and avoiding expensive serialization.\n *\n * Special handling for join operations: When values are tuples of length 2 (common in joins),\n * we unpack them and compare each element individually to maintain proper equality semantics.\n */\n #consolidateKeyed(): MultiSet<T> {\n const consolidated = new Map<string, number>()\n const values = new Map<string, T>()\n\n // Use global object ID generator for consistent reference equality\n\n /**\n * Special handler for tuples (arrays of length 2) commonly produced by join operations.\n * Unpacks the tuple and generates an ID based on both elements to ensure proper\n * consolidation of join results like ['A', null] and [null, 'X'].\n */\n const getTupleId = (tuple: Array<any>): string => {\n if (tuple.length !== 2) {\n throw new Error(`Expected tuple of length 2`)\n }\n const [first, second] = tuple\n return `${globalObjectIdGenerator.getStringId(first)}|${globalObjectIdGenerator.getStringId(second)}`\n }\n\n // Process each item in the multiset\n for (const [data, multiplicity] of this.#inner) {\n // Verify this is still a keyed item (should be [key, value] pair)\n if (!Array.isArray(data) || data.length !== 2) {\n // Found non-keyed item, fall back to unkeyed consolidation\n return this.#consolidateUnkeyed()\n }\n\n const [key, value] = data\n\n // Verify key is string or number as expected for keyed multisets\n if (typeof key !== `string` && typeof key !== `number`) {\n // Found non-string/number key, fall back to unkeyed consolidation\n return this.#consolidateUnkeyed()\n }\n\n // Generate value ID with special handling for join tuples\n let valueId: string\n if (Array.isArray(value) && value.length === 2) {\n // Special case: value is a tuple from join operations\n valueId = getTupleId(value)\n } else {\n // Regular case: use reference/value equality\n valueId = globalObjectIdGenerator.getStringId(value)\n }\n\n // Create composite key and consolidate\n const compositeKey = key + `|` + valueId\n consolidated.set(\n compositeKey,\n (consolidated.get(compositeKey) || 0) + multiplicity\n )\n\n // Store the original data for the first occurrence\n if (!values.has(compositeKey)) {\n values.set(compositeKey, data as T)\n }\n }\n\n // Build result array, filtering out zero multiplicities\n const result: MultiSetArray<T> = []\n for (const [compositeKey, multiplicity] of consolidated) {\n if (multiplicity !== 0) {\n result.push([values.get(compositeKey)!, multiplicity])\n }\n }\n\n return new MultiSet(result)\n }\n\n /**\n * Private method for consolidating unkeyed multisets using the original approach.\n */\n #consolidateUnkeyed(): MultiSet<T> {\n const consolidated = new DefaultMap<string | number, number>(() => 0)\n const values = new Map<string, any>()\n\n let hasString = false\n let hasNumber = false\n let hasOther = false\n for (const [data, _] of this.#inner) {\n if (typeof data === `string`) {\n hasString = true\n } else if (typeof data === `number`) {\n hasNumber = true\n } else {\n hasOther = true\n break\n }\n }\n\n const requireJson = hasOther || (hasString && hasNumber)\n\n for (const [data, multiplicity] of this.#inner) {\n const key = requireJson ? hash(data) : (data as string | number)\n if (requireJson && !values.has(key as string)) {\n values.set(key as string, data)\n }\n consolidated.update(key, (count) => count + multiplicity)\n }\n\n const result: MultiSetArray<T> = []\n for (const [key, multiplicity] of consolidated.entries()) {\n if (multiplicity !== 0) {\n const parsedKey = requireJson ? values.get(key as string) : key\n result.push([parsedKey as T, multiplicity])\n }\n }\n\n return new MultiSet(result)\n }\n\n extend(other: MultiSet<T> | MultiSetArray<T>): void {\n const otherArray = other instanceof MultiSet ? other.getInner() : other\n chunkedArrayPush(this.#inner, otherArray)\n }\n\n getInner(): MultiSetArray<T> {\n return this.#inner\n }\n}\n"],"names":["chunkedArrayPush","globalObjectIdGenerator","DefaultMap","hash"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"multiset.cjs","sources":["../../src/multiset.ts"],"sourcesContent":["import {\n DefaultMap,\n chunkedArrayPush,\n globalObjectIdGenerator,\n} from \"./utils.js\"\nimport { hash } from \"./hashing/index.js\"\n\nexport type MultiSetArray<T> = Array<[T, number]>\nexport type KeyedData<T> = [key: string, value: T]\n\n/**\n * A multiset of data.\n */\nexport class MultiSet<T> {\n #inner: MultiSetArray<T>\n\n constructor(data: MultiSetArray<T> = []) {\n this.#inner = data\n }\n\n toString(indent = false): string {\n return `MultiSet(${JSON.stringify(this.#inner, null, indent ? 2 : undefined)})`\n }\n\n toJSON(): string {\n return JSON.stringify(Array.from(this.getInner()))\n }\n\n static fromJSON<U>(json: string): MultiSet<U> {\n return new MultiSet(JSON.parse(json))\n }\n\n /**\n * Apply a function to all records in the collection.\n */\n map<U>(f: (data: T) => U): MultiSet<U> {\n return new MultiSet(\n this.#inner.map(([data, multiplicity]) => [f(data), multiplicity])\n )\n }\n\n /**\n * Filter out records for which a function f(record) evaluates to False.\n */\n filter(f: (data: T) => boolean): MultiSet<T> {\n return new MultiSet(this.#inner.filter(([data, _]) => f(data)))\n }\n\n /**\n * Negate all multiplicities in the collection.\n */\n negate(): MultiSet<T> {\n return new MultiSet(\n this.#inner.map(([data, multiplicity]) => [data, -multiplicity])\n )\n }\n\n /**\n * Concatenate two collections together.\n */\n concat(other: MultiSet<T>): MultiSet<T> {\n const out: MultiSetArray<T> = []\n chunkedArrayPush(out, this.#inner)\n chunkedArrayPush(out, other.getInner())\n return new MultiSet(out)\n }\n\n /**\n * Produce as output a collection that is logically equivalent to the input\n * but which combines identical instances of the same record into one\n * (record, multiplicity) pair.\n */\n consolidate(): MultiSet<T> {\n // Check if this looks like a keyed multiset (first item is a tuple of length 2)\n if (this.#inner.length > 0) {\n const firstItem = this.#inner[0]?.[0]\n if (Array.isArray(firstItem) && firstItem.length === 2) {\n return this.#consolidateKeyed()\n }\n }\n\n // Fall back to original method for unkeyed data\n return this.#consolidateUnkeyed()\n }\n\n /**\n * Private method for consolidating keyed multisets where keys are strings/numbers\n * and values are compared by reference equality.\n *\n * This method provides significant performance improvements over the hash-based approach\n * by using WeakMap for object reference tracking and avoiding expensive serialization.\n *\n * Special handling for join operations: When values are tuples of length 2 (common in joins),\n * we unpack them and compare each element individually to maintain proper equality semantics.\n */\n #consolidateKeyed(): MultiSet<T> {\n const consolidated = new Map<string, number>()\n const values = new Map<string, T>()\n\n // Use global object ID generator for consistent reference equality\n\n /**\n * Special handler for tuples (arrays of length 2) commonly produced by join operations.\n * Unpacks the tuple and generates an ID based on both elements to ensure proper\n * consolidation of join results like ['A', null] and [null, 'X'].\n */\n const getTupleId = (tuple: Array<any>): string => {\n if (tuple.length !== 2) {\n throw new Error(`Expected tuple of length 2`)\n }\n const [first, second] = tuple\n return `${globalObjectIdGenerator.getStringId(first)}|${globalObjectIdGenerator.getStringId(second)}`\n }\n\n // Process each item in the multiset\n for (const [data, multiplicity] of this.#inner) {\n // Verify this is still a keyed item (should be [key, value] pair)\n if (!Array.isArray(data) || data.length !== 2) {\n // Found non-keyed item, fall back to unkeyed consolidation\n return this.#consolidateUnkeyed()\n }\n\n const [key, value] = data\n\n // Verify key is string or number as expected for keyed multisets\n if (typeof key !== `string` && typeof key !== `number`) {\n // Found non-string/number key, fall back to unkeyed consolidation\n return this.#consolidateUnkeyed()\n }\n\n // Generate value ID with special handling for join tuples\n let valueId: string\n if (Array.isArray(value) && value.length === 2) {\n // Special case: value is a tuple from join operations\n valueId = getTupleId(value)\n } else {\n // Regular case: use reference/value equality\n valueId = globalObjectIdGenerator.getStringId(value)\n }\n\n // Create composite key and consolidate\n const compositeKey = key + `|` + valueId\n consolidated.set(\n compositeKey,\n (consolidated.get(compositeKey) || 0) + multiplicity\n )\n\n // Store the original data for the first occurrence\n if (!values.has(compositeKey)) {\n values.set(compositeKey, data as T)\n }\n }\n\n // Build result array, filtering out zero multiplicities\n const result: MultiSetArray<T> = []\n for (const [compositeKey, multiplicity] of consolidated) {\n if (multiplicity !== 0) {\n result.push([values.get(compositeKey)!, multiplicity])\n }\n }\n\n return new MultiSet(result)\n }\n\n /**\n * Private method for consolidating unkeyed multisets using the original approach.\n */\n #consolidateUnkeyed(): MultiSet<T> {\n const consolidated = new DefaultMap<string | number, number>(() => 0)\n const values = new Map<string, any>()\n\n let hasString = false\n let hasNumber = false\n let hasOther = false\n for (const [data, _] of this.#inner) {\n if (typeof data === `string`) {\n hasString = true\n } else if (typeof data === `number`) {\n hasNumber = true\n } else {\n hasOther = true\n break\n }\n }\n\n const requireJson = hasOther || (hasString && hasNumber)\n\n for (const [data, multiplicity] of this.#inner) {\n const key = requireJson ? hash(data) : (data as string | number)\n if (requireJson && !values.has(key as string)) {\n values.set(key as string, data)\n }\n consolidated.update(key, (count) => count + multiplicity)\n }\n\n const result: MultiSetArray<T> = []\n for (const [key, multiplicity] of consolidated.entries()) {\n if (multiplicity !== 0) {\n const parsedKey = requireJson ? values.get(key as string) : key\n result.push([parsedKey as T, multiplicity])\n }\n }\n\n return new MultiSet(result)\n }\n\n extend(other: MultiSet<T> | MultiSetArray<T>): void {\n const otherArray = other instanceof MultiSet ? other.getInner() : other\n chunkedArrayPush(this.#inner, otherArray)\n }\n\n getInner(): MultiSetArray<T> {\n return this.#inner\n }\n}\n"],"names":["chunkedArrayPush","globalObjectIdGenerator","DefaultMap","hash"],"mappings":"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"}
|
|
@@ -10,8 +10,8 @@ var _by, _values;
|
|
|
10
10
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
11
11
|
const graph = require("../graph.cjs");
|
|
12
12
|
const d2 = require("../d2.cjs");
|
|
13
|
-
const utils = require("../utils.cjs");
|
|
14
13
|
const multiset = require("../multiset.cjs");
|
|
14
|
+
const hash = require("../hashing/hash.cjs");
|
|
15
15
|
class DistinctOperator extends graph.UnaryOperator {
|
|
16
16
|
// keeps track of the number of times each value has been seen
|
|
17
17
|
constructor(id, input, output, by = (value) => value) {
|
|
@@ -26,7 +26,7 @@ class DistinctOperator extends graph.UnaryOperator {
|
|
|
26
26
|
const updatedValues = /* @__PURE__ */ new Map();
|
|
27
27
|
for (const message of this.inputMessages()) {
|
|
28
28
|
for (const [value, diff] of message.getInner()) {
|
|
29
|
-
const hashedValue =
|
|
29
|
+
const hashedValue = hash.hash(__privateGet(this, _by).call(this, value));
|
|
30
30
|
const oldMultiplicity = ((_a = updatedValues.get(hashedValue)) == null ? void 0 : _a[0]) ?? __privateGet(this, _values).get(hashedValue) ?? 0;
|
|
31
31
|
const newMultiplicity = oldMultiplicity + diff;
|
|
32
32
|
updatedValues.set(hashedValue, [newMultiplicity, value]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"distinct.cjs","sources":["../../../src/operators/distinct.ts"],"sourcesContent":["import { DifferenceStreamWriter, UnaryOperator } from \"../graph.js\"\nimport { StreamBuilder } from \"../d2.js\"\nimport { hash } from \"../
|
|
1
|
+
{"version":3,"file":"distinct.cjs","sources":["../../../src/operators/distinct.ts"],"sourcesContent":["import { DifferenceStreamWriter, UnaryOperator } from \"../graph.js\"\nimport { StreamBuilder } from \"../d2.js\"\nimport { hash } from \"../hashing/index.js\"\nimport { MultiSet } from \"../multiset.js\"\nimport type { Hash } from \"../hashing/index.js\"\nimport type { DifferenceStreamReader } from \"../graph.js\"\nimport type { IStreamBuilder } from \"../types.js\"\n\ntype Multiplicity = number\n\n/**\n * Operator that removes duplicates\n */\nexport class DistinctOperator<T> extends UnaryOperator<T> {\n #by: (value: T) => any\n #values: Map<Hash, Multiplicity> // keeps track of the number of times each value has been seen\n\n constructor(\n id: number,\n input: DifferenceStreamReader<T>,\n output: DifferenceStreamWriter<T>,\n by: (value: T) => any = (value: T) => value\n ) {\n super(id, input, output)\n this.#by = by\n this.#values = new Map()\n }\n\n run(): void {\n const updatedValues = new Map<Hash, [Multiplicity, T]>()\n\n // Compute the new multiplicity for each value\n for (const message of this.inputMessages()) {\n for (const [value, diff] of message.getInner()) {\n const hashedValue = hash(this.#by(value))\n\n const oldMultiplicity =\n updatedValues.get(hashedValue)?.[0] ??\n this.#values.get(hashedValue) ??\n 0\n const newMultiplicity = oldMultiplicity + diff\n\n updatedValues.set(hashedValue, [newMultiplicity, value])\n }\n }\n\n const result: Array<[T, number]> = []\n\n // Check which values became visible or disappeared\n for (const [\n hashedValue,\n [newMultiplicity, value],\n ] of updatedValues.entries()) {\n const oldMultiplicity = this.#values.get(hashedValue) ?? 0\n\n if (newMultiplicity === 0) {\n this.#values.delete(hashedValue)\n } else {\n this.#values.set(hashedValue, newMultiplicity)\n }\n\n if (oldMultiplicity <= 0 && newMultiplicity > 0) {\n // The value wasn't present in the stream\n // but with this change it is now present in the stream\n result.push([value, 1])\n } else if (oldMultiplicity > 0 && newMultiplicity <= 0) {\n // The value was present in the stream\n // but with this change it is no longer present in the stream\n result.push([value, -1])\n }\n }\n\n if (result.length > 0) {\n this.output.sendData(new MultiSet(result))\n }\n }\n}\n\n/**\n * Removes duplicate values\n */\nexport function distinct<T>(by: (value: T) => any = (value: T) => value) {\n return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {\n const output = new StreamBuilder<T>(\n stream.graph,\n new DifferenceStreamWriter<T>()\n )\n const operator = new DistinctOperator<T>(\n stream.graph.getNextOperatorId(),\n stream.connectReader(),\n output.writer,\n by\n )\n stream.graph.addOperator(operator)\n stream.graph.addStream(output.connectReader())\n return output\n }\n}\n"],"names":["UnaryOperator","hash","MultiSet","StreamBuilder","DifferenceStreamWriter"],"mappings":";;;;;;;;;;;;;;AAaO,MAAM,yBAA4BA,MAAAA,cAAiB;AAAA;AAAA,EAIxD,YACE,IACA,OACA,QACA,KAAwB,CAAC,UAAa,OACtC;AACA,UAAM,IAAI,OAAO,MAAM;AATzB;AACA;AASE,uBAAK,KAAM;AACX,uBAAK,6BAAc,IAAA;AAAA,EACrB;AAAA,EAEA,MAAY;;AACV,UAAM,oCAAoB,IAAA;AAG1B,eAAW,WAAW,KAAK,iBAAiB;AAC1C,iBAAW,CAAC,OAAO,IAAI,KAAK,QAAQ,YAAY;AAC9C,cAAM,cAAcC,KAAAA,KAAK,mBAAK,KAAL,WAAS,MAAM;AAExC,cAAM,oBACJ,mBAAc,IAAI,WAAW,MAA7B,mBAAiC,OACjC,mBAAK,SAAQ,IAAI,WAAW,KAC5B;AACF,cAAM,kBAAkB,kBAAkB;AAE1C,sBAAc,IAAI,aAAa,CAAC,iBAAiB,KAAK,CAAC;AAAA,MACzD;AAAA,IACF;AAEA,UAAM,SAA6B,CAAA;AAGnC,eAAW;AAAA,MACT;AAAA,MACA,CAAC,iBAAiB,KAAK;AAAA,IAAA,KACpB,cAAc,WAAW;AAC5B,YAAM,kBAAkB,mBAAK,SAAQ,IAAI,WAAW,KAAK;AAEzD,UAAI,oBAAoB,GAAG;AACzB,2BAAK,SAAQ,OAAO,WAAW;AAAA,MACjC,OAAO;AACL,2BAAK,SAAQ,IAAI,aAAa,eAAe;AAAA,MAC/C;AAEA,UAAI,mBAAmB,KAAK,kBAAkB,GAAG;AAG/C,eAAO,KAAK,CAAC,OAAO,CAAC,CAAC;AAAA,MACxB,WAAW,kBAAkB,KAAK,mBAAmB,GAAG;AAGtD,eAAO,KAAK,CAAC,OAAO,EAAE,CAAC;AAAA,MACzB;AAAA,IACF;AAEA,QAAI,OAAO,SAAS,GAAG;AACrB,WAAK,OAAO,SAAS,IAAIC,SAAAA,SAAS,MAAM,CAAC;AAAA,IAC3C;AAAA,EACF;AACF;AA9DE;AACA;AAkEK,SAAS,SAAY,KAAwB,CAAC,UAAa,OAAO;AACvE,SAAO,CAAC,WAAiD;AACvD,UAAM,SAAS,IAAIC,GAAAA;AAAAA,MACjB,OAAO;AAAA,MACP,IAAIC,MAAAA,uBAAA;AAAA,IAA0B;AAEhC,UAAM,WAAW,IAAI;AAAA,MACnB,OAAO,MAAM,kBAAA;AAAA,MACb,OAAO,cAAA;AAAA,MACP,OAAO;AAAA,MACP;AAAA,IAAA;AAEF,WAAO,MAAM,YAAY,QAAQ;AACjC,WAAO,MAAM,UAAU,OAAO,cAAA,CAAe;AAC7C,WAAO;AAAA,EACT;AACF;;;"}
|
|
@@ -81,13 +81,14 @@ function sum(valueExtractor = (v) => v) {
|
|
|
81
81
|
}
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
|
-
function count() {
|
|
84
|
+
function count(valueExtractor = (v) => v) {
|
|
85
85
|
return {
|
|
86
|
-
|
|
86
|
+
// Count only not-null values (the `== null` comparison gives true for both null and undefined)
|
|
87
|
+
preMap: (data) => valueExtractor(data) == null ? 0 : 1,
|
|
87
88
|
reduce: (values) => {
|
|
88
89
|
let totalCount = 0;
|
|
89
|
-
for (const [
|
|
90
|
-
totalCount += multiplicity;
|
|
90
|
+
for (const [nullMultiplier, multiplicity] of values) {
|
|
91
|
+
totalCount += nullMultiplier * multiplicity;
|
|
91
92
|
}
|
|
92
93
|
return totalCount;
|
|
93
94
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"groupBy.cjs","sources":["../../../src/operators/groupBy.ts"],"sourcesContent":["import { map } from \"./map.js\"\nimport { reduce } from \"./reduce.js\"\nimport type { IStreamBuilder, KeyValue } from \"../types.js\"\n\ntype GroupKey = Record<string, unknown>\n\ntype BasicAggregateFunction<T, R, V = unknown> = {\n preMap: (data: T) => V\n reduce: (values: Array<[V, number]>) => V\n postMap?: (result: V) => R\n}\n\ntype PipedAggregateFunction<T, R> = {\n pipe: (stream: IStreamBuilder<T>) => IStreamBuilder<KeyValue<string, R>>\n}\n\ntype AggregateFunction<T, R, V = unknown> =\n | BasicAggregateFunction<T, R, V>\n | PipedAggregateFunction<T, R>\n\ntype ExtractAggregateReturnType<T, A> =\n A extends AggregateFunction<T, infer R, any> ? R : never\n\ntype AggregatesReturnType<T, A> = {\n [K in keyof A]: ExtractAggregateReturnType<T, A[K]>\n}\n\nfunction isPipedAggregateFunction<T, R>(\n aggregate: AggregateFunction<T, R>\n): aggregate is PipedAggregateFunction<T, R> {\n return `pipe` in aggregate\n}\n\n/**\n * Groups data by key and applies multiple aggregate operations\n * @param keyExtractor Function to extract grouping key from data\n * @param aggregates Object mapping aggregate names to aggregate functions\n */\nexport function groupBy<\n T,\n K extends GroupKey,\n A extends Record<string, AggregateFunction<T, any, any>>,\n>(keyExtractor: (data: T) => K, aggregates: A = {} as A) {\n type ResultType = K & AggregatesReturnType<T, A>\n\n const basicAggregates = Object.fromEntries(\n Object.entries(aggregates).filter(\n ([_, aggregate]) => !isPipedAggregateFunction(aggregate)\n )\n ) as Record<string, BasicAggregateFunction<T, any, any>>\n\n // @ts-expect-error - TODO: we don't use this yet, but we will\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const pipedAggregates = Object.fromEntries(\n Object.entries(aggregates).filter(([_, aggregate]) =>\n isPipedAggregateFunction(aggregate)\n )\n ) as Record<string, PipedAggregateFunction<T, any>>\n\n return (\n stream: IStreamBuilder<T>\n ): IStreamBuilder<KeyValue<string, ResultType>> => {\n // Special key to store the original key object\n const KEY_SENTINEL = `__original_key__`\n\n // First map to extract keys and pre-aggregate values\n const withKeysAndValues = stream.pipe(\n map((data) => {\n const key = keyExtractor(data)\n const keyString = JSON.stringify(key)\n\n // Create values object with pre-aggregated values\n const values: Record<string, unknown> = {}\n\n // Store the original key object\n values[KEY_SENTINEL] = key\n\n // Add pre-aggregated values\n for (const [name, aggregate] of Object.entries(basicAggregates)) {\n values[name] = aggregate.preMap(data)\n }\n\n return [keyString, values] as KeyValue<string, Record<string, unknown>>\n })\n )\n\n // Then reduce to compute aggregates\n const reduced = withKeysAndValues.pipe(\n reduce((values) => {\n // Calculate total multiplicity to check if the group should exist\n let totalMultiplicity = 0\n for (const [_, multiplicity] of values) {\n totalMultiplicity += multiplicity\n }\n\n // If total multiplicity is 0 or negative, the group should be removed completely\n if (totalMultiplicity <= 0) {\n return []\n }\n\n const result: Record<string, unknown> = {}\n\n // Get the original key from first value in group\n const originalKey = values[0]?.[0]?.[KEY_SENTINEL]\n result[KEY_SENTINEL] = originalKey\n\n // Apply each aggregate function\n for (const [name, aggregate] of Object.entries(basicAggregates)) {\n const preValues = values.map(\n ([v, m]) => [v[name], m] as [any, number]\n )\n result[name] = aggregate.reduce(preValues)\n }\n\n return [[result, 1]]\n })\n )\n\n // Finally map to extract the key and include all values\n return reduced.pipe(\n map(([keyString, values]) => {\n // Extract the original key\n const key = values[KEY_SENTINEL] as K\n\n // Create intermediate result with key values and aggregate results\n const result: Record<string, unknown> = {}\n\n // Add key properties to result\n Object.assign(result, key)\n\n // Apply postMap if provided\n for (const [name, aggregate] of Object.entries(basicAggregates)) {\n if (aggregate.postMap) {\n result[name] = aggregate.postMap(values[name])\n } else {\n result[name] = values[name]\n }\n }\n\n // Return with the string key instead of the object\n return [keyString, result] as KeyValue<string, ResultType>\n })\n )\n }\n}\n\n/**\n * Creates a sum aggregate function\n */\nexport function sum<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number\n): AggregateFunction<T, number, number> {\n return {\n preMap: (data: T) => valueExtractor(data),\n reduce: (values: Array<[number, number]>) => {\n let total = 0\n for (const [value, multiplicity] of values) {\n total += value * multiplicity\n }\n return total\n },\n }\n}\n\n/**\n * Creates a count aggregate function\n */\nexport function count<T>(): AggregateFunction<T, number, number> {\n return {\n preMap: () => 1,\n reduce: (values: Array<[number, number]>) => {\n let totalCount = 0\n for (const [_, multiplicity] of values) {\n totalCount += multiplicity\n }\n return totalCount\n },\n }\n}\n\n/**\n * Creates an average aggregate function\n */\nexport function avg<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number\n): AggregateFunction<T, number, { sum: number; count: number }> {\n return {\n preMap: (data: T) => ({\n sum: valueExtractor(data),\n count: 0,\n }),\n reduce: (values: Array<[{ sum: number; count: number }, number]>) => {\n let totalSum = 0\n let totalCount = 0\n for (const [value, multiplicity] of values) {\n totalSum += value.sum * multiplicity\n totalCount += multiplicity\n }\n return {\n sum: totalSum,\n count: totalCount,\n }\n },\n postMap: (result: { sum: number; count: number }) => {\n return result.sum / result.count\n },\n }\n}\n\n/**\n * Creates a min aggregate function that computes the minimum value in a group\n * @param valueExtractor Function to extract a numeric value from each data entry\n */\nexport function min<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number\n): AggregateFunction<T, number, number> {\n return {\n preMap: (data: T) => valueExtractor(data),\n reduce: (values: Array<[number, number]>) => {\n let minValue = Number.POSITIVE_INFINITY\n for (const [value, _multiplicity] of values) {\n if (value < minValue) {\n minValue = value\n }\n }\n return minValue === Number.POSITIVE_INFINITY ? 0 : minValue\n },\n }\n}\n\n/**\n * Creates a max aggregate function that computes the maximum value in a group\n * @param valueExtractor Function to extract a numeric value from each data entry\n */\nexport function max<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number\n): AggregateFunction<T, number, number> {\n return {\n preMap: (data: T) => valueExtractor(data),\n reduce: (values: Array<[number, number]>) => {\n let maxValue = Number.NEGATIVE_INFINITY\n for (const [value, _multiplicity] of values) {\n if (value > maxValue) {\n maxValue = value\n }\n }\n return maxValue === Number.NEGATIVE_INFINITY ? 0 : maxValue\n },\n }\n}\n\n/**\n * Creates a median aggregate function that computes the middle value in a sorted group\n * If there's an even number of values, returns the average of the two middle values\n * @param valueExtractor Function to extract a numeric value from each data entry\n */\nexport function median<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number\n): AggregateFunction<T, number, Array<number>> {\n return {\n preMap: (data: T) => [valueExtractor(data)],\n reduce: (values: Array<[Array<number>, number]>) => {\n // Flatten all values, taking multiplicity into account\n const allValues: Array<number> = []\n for (const [valueArray, multiplicity] of values) {\n for (const value of valueArray) {\n // Add each value multiple times based on multiplicity\n for (let i = 0; i < multiplicity; i++) {\n allValues.push(value)\n }\n }\n }\n\n // Return empty array if no values\n if (allValues.length === 0) {\n return []\n }\n\n // Sort values\n allValues.sort((a, b) => a - b)\n\n return allValues\n },\n postMap: (result: Array<number>) => {\n if (result.length === 0) return 0\n\n const mid = Math.floor(result.length / 2)\n\n // If even number of values, average the two middle values\n if (result.length % 2 === 0) {\n return (result[mid - 1]! + result[mid]!) / 2\n }\n\n // If odd number of values, return the middle value\n return result[mid]!\n },\n }\n}\n\n/**\n * Creates a mode aggregate function that computes the most frequent value in a group\n * If multiple values have the same highest frequency, returns the first one encountered\n * @param valueExtractor Function to extract a value from each data entry\n */\nexport function mode<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number\n): AggregateFunction<T, number, Map<number, number>> {\n return {\n preMap: (data: T) => {\n const value = valueExtractor(data)\n const frequencyMap = new Map<number, number>()\n frequencyMap.set(value, 1)\n return frequencyMap\n },\n reduce: (values: Array<[Map<number, number>, number]>) => {\n // Combine all frequency maps\n const combinedMap = new Map<number, number>()\n\n for (const [frequencyMap, multiplicity] of values) {\n for (const [value, frequencyCount] of frequencyMap.entries()) {\n const currentCount = combinedMap.get(value) || 0\n combinedMap.set(value, currentCount + frequencyCount * multiplicity)\n }\n }\n\n return combinedMap\n },\n postMap: (result: Map<number, number>) => {\n if (result.size === 0) return 0\n\n let modeValue = 0\n let maxFrequency = 0\n\n for (const [value, frequency] of result.entries()) {\n if (frequency > maxFrequency) {\n maxFrequency = frequency\n modeValue = value\n }\n }\n\n return modeValue\n },\n }\n}\n\nexport const groupByOperators = {\n sum,\n count,\n avg,\n min,\n max,\n median,\n mode,\n}\n"],"names":["map","reduce"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"groupBy.cjs","sources":["../../../src/operators/groupBy.ts"],"sourcesContent":["import { map } from \"./map.js\"\nimport { reduce } from \"./reduce.js\"\nimport type { IStreamBuilder, KeyValue } from \"../types.js\"\n\ntype GroupKey = Record<string, unknown>\n\ntype BasicAggregateFunction<T, R, V = unknown> = {\n preMap: (data: T) => V\n reduce: (values: Array<[V, number]>) => V\n postMap?: (result: V) => R\n}\n\ntype PipedAggregateFunction<T, R> = {\n pipe: (stream: IStreamBuilder<T>) => IStreamBuilder<KeyValue<string, R>>\n}\n\ntype AggregateFunction<T, R, V = unknown> =\n | BasicAggregateFunction<T, R, V>\n | PipedAggregateFunction<T, R>\n\ntype ExtractAggregateReturnType<T, A> =\n A extends AggregateFunction<T, infer R, any> ? R : never\n\ntype AggregatesReturnType<T, A> = {\n [K in keyof A]: ExtractAggregateReturnType<T, A[K]>\n}\n\nfunction isPipedAggregateFunction<T, R>(\n aggregate: AggregateFunction<T, R>\n): aggregate is PipedAggregateFunction<T, R> {\n return `pipe` in aggregate\n}\n\n/**\n * Groups data by key and applies multiple aggregate operations\n * @param keyExtractor Function to extract grouping key from data\n * @param aggregates Object mapping aggregate names to aggregate functions\n */\nexport function groupBy<\n T,\n K extends GroupKey,\n A extends Record<string, AggregateFunction<T, any, any>>,\n>(keyExtractor: (data: T) => K, aggregates: A = {} as A) {\n type ResultType = K & AggregatesReturnType<T, A>\n\n const basicAggregates = Object.fromEntries(\n Object.entries(aggregates).filter(\n ([_, aggregate]) => !isPipedAggregateFunction(aggregate)\n )\n ) as Record<string, BasicAggregateFunction<T, any, any>>\n\n // @ts-expect-error - TODO: we don't use this yet, but we will\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const pipedAggregates = Object.fromEntries(\n Object.entries(aggregates).filter(([_, aggregate]) =>\n isPipedAggregateFunction(aggregate)\n )\n ) as Record<string, PipedAggregateFunction<T, any>>\n\n return (\n stream: IStreamBuilder<T>\n ): IStreamBuilder<KeyValue<string, ResultType>> => {\n // Special key to store the original key object\n const KEY_SENTINEL = `__original_key__`\n\n // First map to extract keys and pre-aggregate values\n const withKeysAndValues = stream.pipe(\n map((data) => {\n const key = keyExtractor(data)\n const keyString = JSON.stringify(key)\n\n // Create values object with pre-aggregated values\n const values: Record<string, unknown> = {}\n\n // Store the original key object\n values[KEY_SENTINEL] = key\n\n // Add pre-aggregated values\n for (const [name, aggregate] of Object.entries(basicAggregates)) {\n values[name] = aggregate.preMap(data)\n }\n\n return [keyString, values] as KeyValue<string, Record<string, unknown>>\n })\n )\n\n // Then reduce to compute aggregates\n const reduced = withKeysAndValues.pipe(\n reduce((values) => {\n // Calculate total multiplicity to check if the group should exist\n let totalMultiplicity = 0\n for (const [_, multiplicity] of values) {\n totalMultiplicity += multiplicity\n }\n\n // If total multiplicity is 0 or negative, the group should be removed completely\n if (totalMultiplicity <= 0) {\n return []\n }\n\n const result: Record<string, unknown> = {}\n\n // Get the original key from first value in group\n const originalKey = values[0]?.[0]?.[KEY_SENTINEL]\n result[KEY_SENTINEL] = originalKey\n\n // Apply each aggregate function\n for (const [name, aggregate] of Object.entries(basicAggregates)) {\n const preValues = values.map(\n ([v, m]) => [v[name], m] as [any, number]\n )\n result[name] = aggregate.reduce(preValues)\n }\n\n return [[result, 1]]\n })\n )\n\n // Finally map to extract the key and include all values\n return reduced.pipe(\n map(([keyString, values]) => {\n // Extract the original key\n const key = values[KEY_SENTINEL] as K\n\n // Create intermediate result with key values and aggregate results\n const result: Record<string, unknown> = {}\n\n // Add key properties to result\n Object.assign(result, key)\n\n // Apply postMap if provided\n for (const [name, aggregate] of Object.entries(basicAggregates)) {\n if (aggregate.postMap) {\n result[name] = aggregate.postMap(values[name])\n } else {\n result[name] = values[name]\n }\n }\n\n // Return with the string key instead of the object\n return [keyString, result] as KeyValue<string, ResultType>\n })\n )\n }\n}\n\n/**\n * Creates a sum aggregate function\n */\nexport function sum<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number\n): AggregateFunction<T, number, number> {\n return {\n preMap: (data: T) => valueExtractor(data),\n reduce: (values: Array<[number, number]>) => {\n let total = 0\n for (const [value, multiplicity] of values) {\n total += value * multiplicity\n }\n return total\n },\n }\n}\n\n/**\n * Creates a count aggregate function\n */\nexport function count<T>(\n valueExtractor: (value: T) => any = (v) => v\n): AggregateFunction<T, number, number> {\n return {\n // Count only not-null values (the `== null` comparison gives true for both null and undefined)\n preMap: (data: T) => (valueExtractor(data) == null ? 0 : 1),\n reduce: (values: Array<[number, number]>) => {\n let totalCount = 0\n for (const [nullMultiplier, multiplicity] of values) {\n totalCount += nullMultiplier * multiplicity\n }\n return totalCount\n },\n }\n}\n\n/**\n * Creates an average aggregate function\n */\nexport function avg<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number\n): AggregateFunction<T, number, { sum: number; count: number }> {\n return {\n preMap: (data: T) => ({\n sum: valueExtractor(data),\n count: 0,\n }),\n reduce: (values: Array<[{ sum: number; count: number }, number]>) => {\n let totalSum = 0\n let totalCount = 0\n for (const [value, multiplicity] of values) {\n totalSum += value.sum * multiplicity\n totalCount += multiplicity\n }\n return {\n sum: totalSum,\n count: totalCount,\n }\n },\n postMap: (result: { sum: number; count: number }) => {\n return result.sum / result.count\n },\n }\n}\n\n/**\n * Creates a min aggregate function that computes the minimum value in a group\n * @param valueExtractor Function to extract a numeric value from each data entry\n */\nexport function min<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number\n): AggregateFunction<T, number, number> {\n return {\n preMap: (data: T) => valueExtractor(data),\n reduce: (values: Array<[number, number]>) => {\n let minValue = Number.POSITIVE_INFINITY\n for (const [value, _multiplicity] of values) {\n if (value < minValue) {\n minValue = value\n }\n }\n return minValue === Number.POSITIVE_INFINITY ? 0 : minValue\n },\n }\n}\n\n/**\n * Creates a max aggregate function that computes the maximum value in a group\n * @param valueExtractor Function to extract a numeric value from each data entry\n */\nexport function max<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number\n): AggregateFunction<T, number, number> {\n return {\n preMap: (data: T) => valueExtractor(data),\n reduce: (values: Array<[number, number]>) => {\n let maxValue = Number.NEGATIVE_INFINITY\n for (const [value, _multiplicity] of values) {\n if (value > maxValue) {\n maxValue = value\n }\n }\n return maxValue === Number.NEGATIVE_INFINITY ? 0 : maxValue\n },\n }\n}\n\n/**\n * Creates a median aggregate function that computes the middle value in a sorted group\n * If there's an even number of values, returns the average of the two middle values\n * @param valueExtractor Function to extract a numeric value from each data entry\n */\nexport function median<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number\n): AggregateFunction<T, number, Array<number>> {\n return {\n preMap: (data: T) => [valueExtractor(data)],\n reduce: (values: Array<[Array<number>, number]>) => {\n // Flatten all values, taking multiplicity into account\n const allValues: Array<number> = []\n for (const [valueArray, multiplicity] of values) {\n for (const value of valueArray) {\n // Add each value multiple times based on multiplicity\n for (let i = 0; i < multiplicity; i++) {\n allValues.push(value)\n }\n }\n }\n\n // Return empty array if no values\n if (allValues.length === 0) {\n return []\n }\n\n // Sort values\n allValues.sort((a, b) => a - b)\n\n return allValues\n },\n postMap: (result: Array<number>) => {\n if (result.length === 0) return 0\n\n const mid = Math.floor(result.length / 2)\n\n // If even number of values, average the two middle values\n if (result.length % 2 === 0) {\n return (result[mid - 1]! + result[mid]!) / 2\n }\n\n // If odd number of values, return the middle value\n return result[mid]!\n },\n }\n}\n\n/**\n * Creates a mode aggregate function that computes the most frequent value in a group\n * If multiple values have the same highest frequency, returns the first one encountered\n * @param valueExtractor Function to extract a value from each data entry\n */\nexport function mode<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number\n): AggregateFunction<T, number, Map<number, number>> {\n return {\n preMap: (data: T) => {\n const value = valueExtractor(data)\n const frequencyMap = new Map<number, number>()\n frequencyMap.set(value, 1)\n return frequencyMap\n },\n reduce: (values: Array<[Map<number, number>, number]>) => {\n // Combine all frequency maps\n const combinedMap = new Map<number, number>()\n\n for (const [frequencyMap, multiplicity] of values) {\n for (const [value, frequencyCount] of frequencyMap.entries()) {\n const currentCount = combinedMap.get(value) || 0\n combinedMap.set(value, currentCount + frequencyCount * multiplicity)\n }\n }\n\n return combinedMap\n },\n postMap: (result: Map<number, number>) => {\n if (result.size === 0) return 0\n\n let modeValue = 0\n let maxFrequency = 0\n\n for (const [value, frequency] of result.entries()) {\n if (frequency > maxFrequency) {\n maxFrequency = frequency\n modeValue = value\n }\n }\n\n return modeValue\n },\n }\n}\n\nexport const groupByOperators = {\n sum,\n count,\n avg,\n min,\n max,\n median,\n mode,\n}\n"],"names":["map","reduce"],"mappings":"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"}
|
|
@@ -26,7 +26,7 @@ export declare function sum<T>(valueExtractor?: (value: T) => number): Aggregate
|
|
|
26
26
|
/**
|
|
27
27
|
* Creates a count aggregate function
|
|
28
28
|
*/
|
|
29
|
-
export declare function count<T>(): AggregateFunction<T, number, number>;
|
|
29
|
+
export declare function count<T>(valueExtractor?: (value: T) => any): AggregateFunction<T, number, number>;
|
|
30
30
|
/**
|
|
31
31
|
* Creates an average aggregate function
|
|
32
32
|
*/
|
package/dist/cjs/utils.cjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const murmurhash = require("murmurhash-js");
|
|
4
3
|
class DefaultMap extends Map {
|
|
5
4
|
constructor(defaultValue, entries) {
|
|
6
5
|
super(entries);
|
|
@@ -8,7 +7,7 @@ class DefaultMap extends Map {
|
|
|
8
7
|
}
|
|
9
8
|
get(key) {
|
|
10
9
|
if (!this.has(key)) {
|
|
11
|
-
|
|
10
|
+
return this.defaultValue();
|
|
12
11
|
}
|
|
13
12
|
return super.get(key);
|
|
14
13
|
}
|
|
@@ -33,36 +32,6 @@ function chunkedArrayPush(array, other) {
|
|
|
33
32
|
}
|
|
34
33
|
}
|
|
35
34
|
}
|
|
36
|
-
const hashCache = /* @__PURE__ */ new WeakMap();
|
|
37
|
-
function hashReplacer(_key, value) {
|
|
38
|
-
if (typeof value === `bigint`) {
|
|
39
|
-
return String(value);
|
|
40
|
-
} else if (typeof value === `symbol`) {
|
|
41
|
-
return String(value);
|
|
42
|
-
} else if (typeof value === `function`) {
|
|
43
|
-
return String(value);
|
|
44
|
-
} else if (value === void 0) {
|
|
45
|
-
return `undefined`;
|
|
46
|
-
} else if (value instanceof Map) {
|
|
47
|
-
return `Map(${JSON.stringify(Array.from(value.entries()), hashReplacer)})`;
|
|
48
|
-
} else if (value instanceof Set) {
|
|
49
|
-
return `Set(${JSON.stringify(Array.from(value.values()), hashReplacer)})`;
|
|
50
|
-
}
|
|
51
|
-
return value;
|
|
52
|
-
}
|
|
53
|
-
function hash(data) {
|
|
54
|
-
if (data === null || data === void 0 || typeof data !== `object` && typeof data !== `function`) {
|
|
55
|
-
const serialized2 = JSON.stringify(data, hashReplacer);
|
|
56
|
-
return murmurhash.murmur3(serialized2).toString(16);
|
|
57
|
-
}
|
|
58
|
-
if (hashCache.has(data)) {
|
|
59
|
-
return hashCache.get(data);
|
|
60
|
-
}
|
|
61
|
-
const serialized = JSON.stringify(data, hashReplacer);
|
|
62
|
-
const hashValue = murmurhash.murmur3(serialized).toString(16);
|
|
63
|
-
hashCache.set(data, hashValue);
|
|
64
|
-
return hashValue;
|
|
65
|
-
}
|
|
66
35
|
function binarySearch(array, value, comparator) {
|
|
67
36
|
let low = 0;
|
|
68
37
|
let high = array.length;
|
|
@@ -116,10 +85,21 @@ class ObjectIdGenerator {
|
|
|
116
85
|
}
|
|
117
86
|
}
|
|
118
87
|
const globalObjectIdGenerator = new ObjectIdGenerator();
|
|
88
|
+
function* concatIterable(...iterables) {
|
|
89
|
+
for (const iterable of iterables) {
|
|
90
|
+
yield* iterable;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function* mapIterable(it, fn) {
|
|
94
|
+
for (const t of it) {
|
|
95
|
+
yield fn(t);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
119
98
|
exports.DefaultMap = DefaultMap;
|
|
120
99
|
exports.ObjectIdGenerator = ObjectIdGenerator;
|
|
121
100
|
exports.binarySearch = binarySearch;
|
|
122
101
|
exports.chunkedArrayPush = chunkedArrayPush;
|
|
102
|
+
exports.concatIterable = concatIterable;
|
|
123
103
|
exports.globalObjectIdGenerator = globalObjectIdGenerator;
|
|
124
|
-
exports.
|
|
104
|
+
exports.mapIterable = mapIterable;
|
|
125
105
|
//# sourceMappingURL=utils.cjs.map
|
package/dist/cjs/utils.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.cjs","sources":["../../src/utils.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"utils.cjs","sources":["../../src/utils.ts"],"sourcesContent":["/**\n * Simple assertion function for runtime checks.\n * Throws an error if the condition is false.\n */\nexport function assert(\n condition: unknown,\n message?: string\n): asserts condition {\n if (!condition) {\n throw new Error(message || `Assertion failed`)\n }\n}\n\n/**\n * A map that returns a default value for keys that are not present.\n */\nexport class DefaultMap<K, V> extends Map<K, V> {\n constructor(\n private defaultValue: () => V,\n entries?: Iterable<[K, V]>\n ) {\n super(entries)\n }\n\n get(key: K): V {\n if (!this.has(key)) {\n // this.set(key, this.defaultValue())\n return this.defaultValue()\n }\n return super.get(key)!\n }\n\n /**\n * Update the value for a key using a function.\n */\n update(key: K, updater: (value: V) => V): V {\n const value = this.get(key)\n const newValue = updater(value)\n this.set(key, newValue)\n return newValue\n }\n}\n\n// JS engines have various limits on how many args can be passed to a function\n// with a spread operator, so we need to split the operation into chunks\n// 32767 is the max for Chrome 14, all others are higher\n// TODO: investigate the performance of this and other approaches\nconst chunkSize = 30000\nexport function chunkedArrayPush(array: Array<unknown>, other: Array<unknown>) {\n if (other.length <= chunkSize) {\n array.push(...other)\n } else {\n for (let i = 0; i < other.length; i += chunkSize) {\n const chunk = other.slice(i, i + chunkSize)\n array.push(...chunk)\n }\n }\n}\n\nexport function binarySearch<T>(\n array: Array<T>,\n value: T,\n comparator: (a: T, b: T) => number\n): number {\n let low = 0\n let high = array.length\n while (low < high) {\n const mid = Math.floor((low + high) / 2)\n const comparison = comparator(array[mid]!, value)\n if (comparison < 0) {\n low = mid + 1\n } else if (comparison > 0) {\n high = mid\n } else {\n return mid\n }\n }\n return low\n}\n\n/**\n * Utility for generating unique IDs for objects and values.\n * Uses WeakMap for object reference tracking and consistent hashing for primitives.\n */\nexport class ObjectIdGenerator {\n private objectIds = new WeakMap<object, number>()\n private nextId = 0\n\n /**\n * Get a unique identifier for any value.\n * - Objects: Uses WeakMap for reference-based identity\n * - Primitives: Uses consistent string-based hashing\n */\n getId(value: any): number {\n // For primitives, use a simple hash of their string representation\n if (typeof value !== `object` || value === null) {\n const str = String(value)\n let hashValue = 0\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i)\n hashValue = (hashValue << 5) - hashValue + char\n hashValue = hashValue & hashValue // Convert to 32-bit integer\n }\n return hashValue\n }\n\n // For objects, use WeakMap to assign unique IDs\n if (!this.objectIds.has(value)) {\n this.objectIds.set(value, this.nextId++)\n }\n return this.objectIds.get(value)!\n }\n\n /**\n * Get a string representation of the ID for use in composite keys.\n */\n getStringId(value: any): string {\n if (value === null) return `null`\n if (value === undefined) return `undefined`\n if (typeof value !== `object`) return `str_${String(value)}`\n\n return `obj_${this.getId(value)}`\n }\n}\n\n/**\n * Global instance for cases where a shared object ID space is needed.\n */\nexport const globalObjectIdGenerator = new ObjectIdGenerator()\n\nexport function* concatIterable<T>(\n ...iterables: Array<Iterable<T>>\n): Iterable<T> {\n for (const iterable of iterables) {\n yield* iterable\n }\n}\n\nexport function* mapIterable<T, U>(\n it: Iterable<T>,\n fn: (t: T) => U\n): Iterable<U> {\n for (const t of it) {\n yield fn(t)\n }\n}\n"],"names":[],"mappings":";;AAgBO,MAAM,mBAAyB,IAAU;AAAA,EAC9C,YACU,cACR,SACA;AACA,UAAM,OAAO;AAHL,SAAA,eAAA;AAAA,EAIV;AAAA,EAEA,IAAI,KAAW;AACb,QAAI,CAAC,KAAK,IAAI,GAAG,GAAG;AAElB,aAAO,KAAK,aAAA;AAAA,IACd;AACA,WAAO,MAAM,IAAI,GAAG;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,KAAQ,SAA6B;AAC1C,UAAM,QAAQ,KAAK,IAAI,GAAG;AAC1B,UAAM,WAAW,QAAQ,KAAK;AAC9B,SAAK,IAAI,KAAK,QAAQ;AACtB,WAAO;AAAA,EACT;AACF;AAMA,MAAM,YAAY;AACX,SAAS,iBAAiB,OAAuB,OAAuB;AAC7E,MAAI,MAAM,UAAU,WAAW;AAC7B,UAAM,KAAK,GAAG,KAAK;AAAA,EACrB,OAAO;AACL,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,WAAW;AAChD,YAAM,QAAQ,MAAM,MAAM,GAAG,IAAI,SAAS;AAC1C,YAAM,KAAK,GAAG,KAAK;AAAA,IACrB;AAAA,EACF;AACF;AAEO,SAAS,aACd,OACA,OACA,YACQ;AACR,MAAI,MAAM;AACV,MAAI,OAAO,MAAM;AACjB,SAAO,MAAM,MAAM;AACjB,UAAM,MAAM,KAAK,OAAO,MAAM,QAAQ,CAAC;AACvC,UAAM,aAAa,WAAW,MAAM,GAAG,GAAI,KAAK;AAChD,QAAI,aAAa,GAAG;AAClB,YAAM,MAAM;AAAA,IACd,WAAW,aAAa,GAAG;AACzB,aAAO;AAAA,IACT,OAAO;AACL,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAMO,MAAM,kBAAkB;AAAA,EAAxB,cAAA;AACL,SAAQ,gCAAgB,QAAA;AACxB,SAAQ,SAAS;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOjB,MAAM,OAAoB;AAExB,QAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,YAAM,MAAM,OAAO,KAAK;AACxB,UAAI,YAAY;AAChB,eAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,cAAM,OAAO,IAAI,WAAW,CAAC;AAC7B,qBAAa,aAAa,KAAK,YAAY;AAC3C,oBAAY,YAAY;AAAA,MAC1B;AACA,aAAO;AAAA,IACT;AAGA,QAAI,CAAC,KAAK,UAAU,IAAI,KAAK,GAAG;AAC9B,WAAK,UAAU,IAAI,OAAO,KAAK,QAAQ;AAAA,IACzC;AACA,WAAO,KAAK,UAAU,IAAI,KAAK;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,OAAoB;AAC9B,QAAI,UAAU,KAAM,QAAO;AAC3B,QAAI,UAAU,OAAW,QAAO;AAChC,QAAI,OAAO,UAAU,iBAAiB,OAAO,OAAO,KAAK,CAAC;AAE1D,WAAO,OAAO,KAAK,MAAM,KAAK,CAAC;AAAA,EACjC;AACF;AAKO,MAAM,0BAA0B,IAAI,kBAAA;AAEpC,UAAU,kBACZ,WACU;AACb,aAAW,YAAY,WAAW;AAChC,WAAO;AAAA,EACT;AACF;AAEO,UAAU,YACf,IACA,IACa;AACb,aAAW,KAAK,IAAI;AAClB,UAAM,GAAG,CAAC;AAAA,EACZ;AACF;;;;;;;;"}
|
package/dist/cjs/utils.d.cts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple assertion function for runtime checks.
|
|
3
|
+
* Throws an error if the condition is false.
|
|
4
|
+
*/
|
|
5
|
+
export declare function assert(condition: unknown, message?: string): asserts condition;
|
|
1
6
|
/**
|
|
2
7
|
* A map that returns a default value for keys that are not present.
|
|
3
8
|
*/
|
|
@@ -11,10 +16,6 @@ export declare class DefaultMap<K, V> extends Map<K, V> {
|
|
|
11
16
|
update(key: K, updater: (value: V) => V): V;
|
|
12
17
|
}
|
|
13
18
|
export declare function chunkedArrayPush(array: Array<unknown>, other: Array<unknown>): void;
|
|
14
|
-
/**
|
|
15
|
-
* A hash method that caches the hash of a value in a week map
|
|
16
|
-
*/
|
|
17
|
-
export declare function hash(data: any): string;
|
|
18
19
|
export declare function binarySearch<T>(array: Array<T>, value: T, comparator: (a: T, b: T) => number): number;
|
|
19
20
|
/**
|
|
20
21
|
* Utility for generating unique IDs for objects and values.
|
|
@@ -38,3 +39,5 @@ export declare class ObjectIdGenerator {
|
|
|
38
39
|
* Global instance for cases where a shared object ID space is needed.
|
|
39
40
|
*/
|
|
40
41
|
export declare const globalObjectIdGenerator: ObjectIdGenerator;
|
|
42
|
+
export declare function concatIterable<T>(...iterables: Array<Iterable<T>>): Iterable<T>;
|
|
43
|
+
export declare function mapIterable<T, U>(it: Iterable<T>, fn: (t: T) => U): Iterable<U>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __typeError = (msg) => {
|
|
3
|
+
throw TypeError(msg);
|
|
4
|
+
};
|
|
5
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
6
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
7
|
+
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
8
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
9
|
+
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
10
|
+
var _inner, _ValueIndex_instances, setMultiplicity_fn;
|
|
11
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
12
|
+
const hash = require("./hashing/hash.cjs");
|
|
13
|
+
class ValueIndex {
|
|
14
|
+
// Maps key to the value and its multiplicity
|
|
15
|
+
constructor() {
|
|
16
|
+
__privateAdd(this, _ValueIndex_instances);
|
|
17
|
+
__privateAdd(this, _inner);
|
|
18
|
+
__privateSet(this, _inner, /* @__PURE__ */ new Map());
|
|
19
|
+
}
|
|
20
|
+
toString(indent = false) {
|
|
21
|
+
return `ValueIndex(${JSON.stringify(
|
|
22
|
+
[...__privateGet(this, _inner).entries()],
|
|
23
|
+
void 0,
|
|
24
|
+
indent ? 2 : void 0
|
|
25
|
+
)})`;
|
|
26
|
+
}
|
|
27
|
+
get(key) {
|
|
28
|
+
return __privateGet(this, _inner).get(key);
|
|
29
|
+
}
|
|
30
|
+
getMultiplicity(key) {
|
|
31
|
+
var _a;
|
|
32
|
+
return ((_a = this.get(key)) == null ? void 0 : _a[1]) ?? 0;
|
|
33
|
+
}
|
|
34
|
+
entries() {
|
|
35
|
+
return __privateGet(this, _inner).entries();
|
|
36
|
+
}
|
|
37
|
+
has(key) {
|
|
38
|
+
return __privateGet(this, _inner).has(key);
|
|
39
|
+
}
|
|
40
|
+
delete(key) {
|
|
41
|
+
__privateGet(this, _inner).delete(key);
|
|
42
|
+
}
|
|
43
|
+
get size() {
|
|
44
|
+
return __privateGet(this, _inner).size;
|
|
45
|
+
}
|
|
46
|
+
addValue(key, v) {
|
|
47
|
+
const [value, multiplicity] = v;
|
|
48
|
+
if (multiplicity === 0) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (this.has(key)) {
|
|
52
|
+
const [currValue, currMultiplicity] = this.get(key);
|
|
53
|
+
if (hash.hash(value) === hash.hash(currValue)) {
|
|
54
|
+
__privateMethod(this, _ValueIndex_instances, setMultiplicity_fn).call(this, key, value, currMultiplicity + multiplicity);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Cannot add value for key ${key} because it already exists in ValueIndex with a different value`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
__privateGet(this, _inner).set(key, [value, multiplicity]);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
_inner = new WeakMap();
|
|
65
|
+
_ValueIndex_instances = new WeakSet();
|
|
66
|
+
setMultiplicity_fn = function(key, value, multiplicity) {
|
|
67
|
+
if (multiplicity === 0) {
|
|
68
|
+
__privateGet(this, _inner).delete(key);
|
|
69
|
+
} else {
|
|
70
|
+
__privateGet(this, _inner).set(key, [value, multiplicity]);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
exports.ValueIndex = ValueIndex;
|
|
74
|
+
//# sourceMappingURL=valueIndex.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"valueIndex.cjs","sources":["../../src/valueIndex.ts"],"sourcesContent":["import { hash } from \"./hashing/index.js\"\n\n/**\n * A map from a difference collection trace's keys -> (value, multiplicities) that changed.\n * Used in operations like join and reduce where the operation needs to\n * exploit the key-value structure of the data to run efficiently.\n */\nexport class ValueIndex<K, V> {\n #inner: Map<K, [V, number]> // Maps key to the value and its multiplicity\n\n constructor() {\n this.#inner = new Map()\n }\n\n toString(indent = false): string {\n return `ValueIndex(${JSON.stringify(\n [...this.#inner.entries()],\n undefined,\n indent ? 2 : undefined\n )})`\n }\n\n get(key: K): [V, number] | undefined {\n return this.#inner.get(key)\n }\n\n getMultiplicity(key: K): number {\n return this.get(key)?.[1] ?? 0\n }\n\n entries() {\n return this.#inner.entries()\n }\n\n has(key: K): boolean {\n return this.#inner.has(key)\n }\n\n delete(key: K): void {\n this.#inner.delete(key)\n }\n\n get size(): number {\n return this.#inner.size\n }\n\n addValue(key: K, v: [V, number]): void {\n const [value, multiplicity] = v\n\n if (multiplicity === 0) {\n return\n }\n\n if (this.has(key)) {\n const [currValue, currMultiplicity] = this.get(key)!\n if (hash(value) === hash(currValue)) {\n // Update the multiplicity\n this.#setMultiplicity(key, value, currMultiplicity + multiplicity)\n return\n }\n // Different value, not allowed.\n // ValueIndex only supports one value per key.\n throw new Error(\n `Cannot add value for key ${key} because it already exists in ValueIndex with a different value`\n )\n }\n\n this.#inner.set(key, [value, multiplicity])\n }\n\n #setMultiplicity(key: K, value: V, multiplicity: number): void {\n if (multiplicity === 0) {\n this.#inner.delete(key)\n } else {\n this.#inner.set(key, [value, multiplicity])\n }\n }\n}\n"],"names":["hash"],"mappings":";;;;;;;;;;;;AAOO,MAAM,WAAiB;AAAA;AAAA,EAG5B,cAAc;AAHT;AACL;AAGE,uBAAK,4BAAa,IAAA;AAAA,EACpB;AAAA,EAEA,SAAS,SAAS,OAAe;AAC/B,WAAO,cAAc,KAAK;AAAA,MACxB,CAAC,GAAG,mBAAK,QAAO,SAAS;AAAA,MACzB;AAAA,MACA,SAAS,IAAI;AAAA,IAAA,CACd;AAAA,EACH;AAAA,EAEA,IAAI,KAAiC;AACnC,WAAO,mBAAK,QAAO,IAAI,GAAG;AAAA,EAC5B;AAAA,EAEA,gBAAgB,KAAgB;;AAC9B,aAAO,UAAK,IAAI,GAAG,MAAZ,mBAAgB,OAAM;AAAA,EAC/B;AAAA,EAEA,UAAU;AACR,WAAO,mBAAK,QAAO,QAAA;AAAA,EACrB;AAAA,EAEA,IAAI,KAAiB;AACnB,WAAO,mBAAK,QAAO,IAAI,GAAG;AAAA,EAC5B;AAAA,EAEA,OAAO,KAAc;AACnB,uBAAK,QAAO,OAAO,GAAG;AAAA,EACxB;AAAA,EAEA,IAAI,OAAe;AACjB,WAAO,mBAAK,QAAO;AAAA,EACrB;AAAA,EAEA,SAAS,KAAQ,GAAsB;AACrC,UAAM,CAAC,OAAO,YAAY,IAAI;AAE9B,QAAI,iBAAiB,GAAG;AACtB;AAAA,IACF;AAEA,QAAI,KAAK,IAAI,GAAG,GAAG;AACjB,YAAM,CAAC,WAAW,gBAAgB,IAAI,KAAK,IAAI,GAAG;AAClD,UAAIA,KAAAA,KAAK,KAAK,MAAMA,KAAAA,KAAK,SAAS,GAAG;AAEnC,8BAAK,2CAAL,WAAsB,KAAK,OAAO,mBAAmB;AACrD;AAAA,MACF;AAGA,YAAM,IAAI;AAAA,QACR,4BAA4B,GAAG;AAAA,MAAA;AAAA,IAEnC;AAEA,uBAAK,QAAO,IAAI,KAAK,CAAC,OAAO,YAAY,CAAC;AAAA,EAC5C;AASF;AArEE;AADK;AA+DL,qBAAA,SAAiB,KAAQ,OAAU,cAA4B;AAC7D,MAAI,iBAAiB,GAAG;AACtB,uBAAK,QAAO,OAAO,GAAG;AAAA,EACxB,OAAO;AACL,uBAAK,QAAO,IAAI,KAAK,CAAC,OAAO,YAAY,CAAC;AAAA,EAC5C;AACF;;"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A map from a difference collection trace's keys -> (value, multiplicities) that changed.
|
|
3
|
+
* Used in operations like join and reduce where the operation needs to
|
|
4
|
+
* exploit the key-value structure of the data to run efficiently.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ValueIndex<K, V> {
|
|
7
|
+
#private;
|
|
8
|
+
constructor();
|
|
9
|
+
toString(indent?: boolean): string;
|
|
10
|
+
get(key: K): [V, number] | undefined;
|
|
11
|
+
getMultiplicity(key: K): number;
|
|
12
|
+
entries(): MapIterator<[K, [V, number]]>;
|
|
13
|
+
has(key: K): boolean;
|
|
14
|
+
delete(key: K): void;
|
|
15
|
+
get size(): number;
|
|
16
|
+
addValue(key: K, v: [V, number]): void;
|
|
17
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { DefaultMap } from './utils.js';
|
|
2
|
+
/**
|
|
3
|
+
* A map from a difference collection trace's keys -> (value, multiplicities) that changed.
|
|
4
|
+
* Used in operations like join and reduce where the operation needs to
|
|
5
|
+
* exploit the key-value structure of the data to run efficiently.
|
|
6
|
+
*/
|
|
7
|
+
export declare class HashIndex<K, V> {
|
|
8
|
+
#private;
|
|
9
|
+
constructor();
|
|
10
|
+
toString(indent?: boolean): string;
|
|
11
|
+
get(key: K): Array<[V, number]>;
|
|
12
|
+
getMultiplicity(key: K, value: V): number;
|
|
13
|
+
entries(): MapIterator<[K, DefaultMap<number, [V, number]>]>;
|
|
14
|
+
entriesIterator(): Generator<[K, [V, number]]>;
|
|
15
|
+
has(key: K): boolean;
|
|
16
|
+
delete(key: K): void;
|
|
17
|
+
get size(): number;
|
|
18
|
+
/**
|
|
19
|
+
* Adds a value to the index and does not return anything
|
|
20
|
+
* except if the addition caused the value to be removed
|
|
21
|
+
* and the key to be left with only a single value.
|
|
22
|
+
* In that case, we return the single remaining value.
|
|
23
|
+
*/
|
|
24
|
+
addValue(key: K, value: [V, number]): [V, number] | void;
|
|
25
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
var __typeError = (msg) => {
|
|
2
|
+
throw TypeError(msg);
|
|
3
|
+
};
|
|
4
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
5
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
6
|
+
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
7
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
8
|
+
var _inner;
|
|
9
|
+
import { DefaultMap } from "./utils.js";
|
|
10
|
+
import { hash } from "./hashing/hash.js";
|
|
11
|
+
class HashIndex {
|
|
12
|
+
constructor() {
|
|
13
|
+
__privateAdd(this, _inner);
|
|
14
|
+
__privateSet(this, _inner, new DefaultMap(
|
|
15
|
+
() => new DefaultMap(() => [void 0, 0])
|
|
16
|
+
));
|
|
17
|
+
}
|
|
18
|
+
toString(indent = false) {
|
|
19
|
+
return `HashIndex(${JSON.stringify(
|
|
20
|
+
[...__privateGet(this, _inner)].map(([k, valueMap]) => [k, [...valueMap]]),
|
|
21
|
+
void 0,
|
|
22
|
+
indent ? 2 : void 0
|
|
23
|
+
)})`;
|
|
24
|
+
}
|
|
25
|
+
get(key) {
|
|
26
|
+
const valueMap = __privateGet(this, _inner).get(key);
|
|
27
|
+
return [...valueMap.values()];
|
|
28
|
+
}
|
|
29
|
+
getMultiplicity(key, value) {
|
|
30
|
+
const valueMap = __privateGet(this, _inner).get(key);
|
|
31
|
+
const valueHash = hash(value);
|
|
32
|
+
const [, multiplicity] = valueMap.get(valueHash);
|
|
33
|
+
return multiplicity;
|
|
34
|
+
}
|
|
35
|
+
entries() {
|
|
36
|
+
return __privateGet(this, _inner).entries();
|
|
37
|
+
}
|
|
38
|
+
*entriesIterator() {
|
|
39
|
+
for (const [key, valueMap] of __privateGet(this, _inner).entries()) {
|
|
40
|
+
for (const [_valueHash, [value, multiplicity]] of valueMap.entries()) {
|
|
41
|
+
yield [key, [value, multiplicity]];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
has(key) {
|
|
46
|
+
return __privateGet(this, _inner).has(key);
|
|
47
|
+
}
|
|
48
|
+
delete(key) {
|
|
49
|
+
__privateGet(this, _inner).delete(key);
|
|
50
|
+
}
|
|
51
|
+
get size() {
|
|
52
|
+
return __privateGet(this, _inner).size;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Adds a value to the index and does not return anything
|
|
56
|
+
* except if the addition caused the value to be removed
|
|
57
|
+
* and the key to be left with only a single value.
|
|
58
|
+
* In that case, we return the single remaining value.
|
|
59
|
+
*/
|
|
60
|
+
addValue(key, value) {
|
|
61
|
+
const [val, multiplicity] = value;
|
|
62
|
+
const valueMap = __privateGet(this, _inner).get(key);
|
|
63
|
+
const valueHash = hash(val);
|
|
64
|
+
const [, existingMultiplicity] = valueMap.get(valueHash);
|
|
65
|
+
const newMultiplicity = existingMultiplicity + multiplicity;
|
|
66
|
+
if (multiplicity !== 0) {
|
|
67
|
+
if (newMultiplicity === 0) {
|
|
68
|
+
valueMap.delete(valueHash);
|
|
69
|
+
if (valueMap.size === 1) {
|
|
70
|
+
return valueMap.entries().next().value[1];
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
valueMap.set(valueHash, [val, newMultiplicity]);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
__privateGet(this, _inner).set(key, valueMap);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
_inner = new WeakMap();
|
|
80
|
+
export {
|
|
81
|
+
HashIndex
|
|
82
|
+
};
|
|
83
|
+
//# sourceMappingURL=hashIndex.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hashIndex.js","sources":["../../src/hashIndex.ts"],"sourcesContent":["import { DefaultMap } from \"./utils.js\"\nimport { hash } from \"./hashing/index.js\"\nimport type { Hash } from \"./hashing/index.js\"\n\n/**\n * A map from a difference collection trace's keys -> (value, multiplicities) that changed.\n * Used in operations like join and reduce where the operation needs to\n * exploit the key-value structure of the data to run efficiently.\n */\nexport class HashIndex<K, V> {\n #inner: DefaultMap<K, DefaultMap<Hash, [V, number]>>\n\n constructor() {\n this.#inner = new DefaultMap<K, DefaultMap<Hash, [V, number]>>(\n () => new DefaultMap<Hash, [V, number]>(() => [undefined as any as V, 0])\n )\n // #inner is as map of:\n // {\n // [key]: {\n // [hash(value)]: [value, multiplicity]\n // }\n // }\n }\n\n toString(indent = false): string {\n return `HashIndex(${JSON.stringify(\n [...this.#inner].map(([k, valueMap]) => [k, [...valueMap]]),\n undefined,\n indent ? 2 : undefined\n )})`\n }\n\n get(key: K): Array<[V, number]> {\n const valueMap = this.#inner.get(key)\n return [...valueMap.values()]\n }\n\n getMultiplicity(key: K, value: V): number {\n const valueMap = this.#inner.get(key)\n const valueHash = hash(value)\n const [, multiplicity] = valueMap.get(valueHash)\n return multiplicity\n }\n\n entries() {\n return this.#inner.entries()\n }\n\n *entriesIterator(): Generator<[K, [V, number]]> {\n for (const [key, valueMap] of this.#inner.entries()) {\n for (const [_valueHash, [value, multiplicity]] of valueMap.entries()) {\n yield [key, [value, multiplicity]]\n }\n }\n }\n\n has(key: K): boolean {\n return this.#inner.has(key)\n }\n\n delete(key: K): void {\n this.#inner.delete(key)\n }\n\n get size(): number {\n return this.#inner.size\n }\n\n /**\n * Adds a value to the index and does not return anything\n * except if the addition caused the value to be removed\n * and the key to be left with only a single value.\n * In that case, we return the single remaining value.\n */\n addValue(key: K, value: [V, number]): [V, number] | void {\n const [val, multiplicity] = value\n const valueMap = this.#inner.get(key)\n const valueHash = hash(val)\n const [, existingMultiplicity] = valueMap.get(valueHash)\n const newMultiplicity = existingMultiplicity + multiplicity\n if (multiplicity !== 0) {\n if (newMultiplicity === 0) {\n valueMap.delete(valueHash)\n if (valueMap.size === 1) {\n // Signal that the key only has a single remaining value\n return valueMap.entries().next().value![1]\n }\n } else {\n valueMap.set(valueHash, [val, newMultiplicity])\n }\n }\n this.#inner.set(key, valueMap)\n }\n}\n"],"names":[],"mappings":";;;;;;;;;;AASO,MAAM,UAAgB;AAAA,EAG3B,cAAc;AAFd;AAGE,uBAAK,QAAS,IAAI;AAAA,MAChB,MAAM,IAAI,WAA8B,MAAM,CAAC,QAAuB,CAAC,CAAC;AAAA,IAAA;AAAA,EAQ5E;AAAA,EAEA,SAAS,SAAS,OAAe;AAC/B,WAAO,aAAa,KAAK;AAAA,MACvB,CAAC,GAAG,mBAAK,OAAM,EAAE,IAAI,CAAC,CAAC,GAAG,QAAQ,MAAM,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC;AAAA,MAC1D;AAAA,MACA,SAAS,IAAI;AAAA,IAAA,CACd;AAAA,EACH;AAAA,EAEA,IAAI,KAA4B;AAC9B,UAAM,WAAW,mBAAK,QAAO,IAAI,GAAG;AACpC,WAAO,CAAC,GAAG,SAAS,QAAQ;AAAA,EAC9B;AAAA,EAEA,gBAAgB,KAAQ,OAAkB;AACxC,UAAM,WAAW,mBAAK,QAAO,IAAI,GAAG;AACpC,UAAM,YAAY,KAAK,KAAK;AAC5B,UAAM,CAAA,EAAG,YAAY,IAAI,SAAS,IAAI,SAAS;AAC/C,WAAO;AAAA,EACT;AAAA,EAEA,UAAU;AACR,WAAO,mBAAK,QAAO,QAAA;AAAA,EACrB;AAAA,EAEA,CAAC,kBAA+C;AAC9C,eAAW,CAAC,KAAK,QAAQ,KAAK,mBAAK,QAAO,WAAW;AACnD,iBAAW,CAAC,YAAY,CAAC,OAAO,YAAY,CAAC,KAAK,SAAS,WAAW;AACpE,cAAM,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC;AAAA,MACnC;AAAA,IACF;AAAA,EACF;AAAA,EAEA,IAAI,KAAiB;AACnB,WAAO,mBAAK,QAAO,IAAI,GAAG;AAAA,EAC5B;AAAA,EAEA,OAAO,KAAc;AACnB,uBAAK,QAAO,OAAO,GAAG;AAAA,EACxB;AAAA,EAEA,IAAI,OAAe;AACjB,WAAO,mBAAK,QAAO;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,SAAS,KAAQ,OAAwC;AACvD,UAAM,CAAC,KAAK,YAAY,IAAI;AAC5B,UAAM,WAAW,mBAAK,QAAO,IAAI,GAAG;AACpC,UAAM,YAAY,KAAK,GAAG;AAC1B,UAAM,CAAA,EAAG,oBAAoB,IAAI,SAAS,IAAI,SAAS;AACvD,UAAM,kBAAkB,uBAAuB;AAC/C,QAAI,iBAAiB,GAAG;AACtB,UAAI,oBAAoB,GAAG;AACzB,iBAAS,OAAO,SAAS;AACzB,YAAI,SAAS,SAAS,GAAG;AAEvB,iBAAO,SAAS,QAAA,EAAU,KAAA,EAAO,MAAO,CAAC;AAAA,QAC3C;AAAA,MACF,OAAO;AACL,iBAAS,IAAI,WAAW,CAAC,KAAK,eAAe,CAAC;AAAA,MAChD;AAAA,IACF;AACA,uBAAK,QAAO,IAAI,KAAK,QAAQ;AAAA,EAC/B;AACF;AAnFE;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function hash(input: any): number;
|