{"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  add(item: T, multiplicity: number): void {\n    if (multiplicity !== 0) {\n      this.#inner.push([item, multiplicity])\n    }\n  }\n\n  getInner(): MultiSetArray<T> {\n    return this.#inner\n  }\n}\n"],"names":["chunkedArrayPush","globalObjectIdGenerator","DefaultMap","hash"],"mappings":"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"}