fp-search-algorithms
Version:
Functional Programming Style Search Algorithms and Unordered Containers
1 lines • 152 kB
Source Map (JSON)
{"version":3,"file":"index.cjs","sources":["../src/helpers/isEqual.ts","../src/structures/internal/hashing.ts","../src/structures/internal/hashTree.ts","../src/structures/hashMap.ts","../src/structures/priorityQueue.ts","../src/searches/internal/createPath.ts","../src/searches/aStar.ts","../src/structures/hashSet.ts","../src/searches/breadthFirst.ts","../src/searches/depthFirst.ts","../src/searches/dijkstra.ts","../src/searches/yen.ts","../src/structures/directedGraph.ts"],"sourcesContent":["/* eslint-disable complexity */\n\nconst unequalDates = (a: Date, b: Date) => {\n return a instanceof Date && (a > b || a < b);\n};\n\nconst unequalBuffers = (a: Buffer, b: Buffer): boolean => {\n return (\n a.buffer instanceof ArrayBuffer &&\n (a.BYTES_PER_ELEMENT as unknown as boolean) &&\n !(a.byteLength === b.byteLength && a.every((n, i) => n === b[i]))\n );\n};\n\nconst unequalArrays = <T>(a: readonly T[], b: readonly T[]) => {\n return Array.isArray(a) && a.length !== b.length;\n};\n\nconst unequalMaps = <K, T>(a: Map<K, T>, b: Map<K, T>) => {\n return a instanceof Map && a.size !== b.size;\n};\n\nconst unequalSets = <T>(a: Set<T>, b: Set<T>) => {\n return a instanceof Set && (a.size !== b.size || [...a].some(e => !b.has(e)));\n};\n\nconst unequalRegExps = (a: RegExp, b: RegExp) => {\n return a instanceof RegExp && (a.source !== b.source || a.flags !== b.flags);\n};\n\nconst isObject = (a: unknown): a is object => {\n return typeof a === 'object' && a !== null;\n};\n\nconst structurallyCompatibleObjects = (a: object, b: object) => {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (typeof a !== 'object' && typeof b !== 'object' && (!a || !b)) return false;\n\n const nonstructural = [Promise, WeakSet, WeakMap, Function];\n if (nonstructural.some(c => a instanceof c)) return false;\n\n return a.constructor === b.constructor;\n};\n\n/**\n * Check for equality by structure of two values\n * * Returns true if strict equality (`===`) returns true\n * * Values of different `typeof` return `false`\n * * Objects with different constructors return `false`\n * * Dates return true if both `>` and `<` return false\n * * ArrayBuffers return true when byteLength are equal and if values at all indexes are equal\n * * Arrays return true when lengths are equal and when values at all indexes pass `isEqual()` recursively\n * * Sets returns true when both are empty or when all keys equal on both\n * * Maps returns true when both are empty, when all keys equal on both, and when those key's values pass `isEqual()` recursively\n * * Dispatches to first argument's prototype method `equals: (other) => boolean` if exists\n * * Objects return true when both share same enumerable keys and all key's values pass `isEqual()` recursively\n *\n * Exceptions:\n * * Functions, Promises, WeakSets, and WeakMaps are checked by reference\n *\n * Notes:\n * * `isEqual({}, Object.create(null))` will always be `false`, regardless of keys/values because they don't share the same constructor\n *\n * @category Helpers\n * @returns boolean indicating whether the values are equal in value, structure, or reference\n */\nexport const isEqual = <T>(x: T, y: T) => {\n const values: unknown[] = [x, y];\n\n while (values.length) {\n const a = values.pop();\n const b = values.pop();\n if (a === b) continue;\n\n if (!isObject(a) || !isObject(b)) return false;\n const unequal =\n !structurallyCompatibleObjects(a, b) ||\n unequalDates(a as unknown as Date, b as unknown as Date) ||\n unequalBuffers(a as unknown as Buffer, b as unknown as Buffer) ||\n unequalArrays(a as unknown[], b as unknown[]) ||\n unequalMaps(a as unknown as Map<unknown, unknown>, b as unknown as Map<unknown, unknown>) ||\n unequalSets(a as unknown as Set<unknown>, b as unknown as Set<unknown>) ||\n unequalRegExps(a as unknown as RegExp, b as unknown as RegExp);\n if (unequal) return false;\n\n const proto = Object.getPrototypeOf(a) as { equals?: (o: T) => boolean } | null;\n if (proto !== null && typeof proto.equals === 'function') {\n try {\n if ((a as { equals: (o: unknown) => boolean }).equals(b)) continue;\n else return false;\n } catch {\n // fall-through\n }\n }\n\n if (a instanceof Map) {\n if (!(b instanceof Map)) return false;\n for (const k of a.keys()) {\n values.push(a.get(k), b.get(k));\n }\n } else {\n // assume a and b are objects\n const aKeys = Object.keys(a);\n const bKeys = Object.keys(b);\n const bKeysSet = new Set(bKeys);\n\n if (aKeys.length !== bKeys.length) return false;\n\n const extra = a instanceof globalThis.Error ? ['message'] : [];\n for (const k of [...extra, ...aKeys]) {\n // @ts-expect-error\n values.push(a[k], b[k]);\n bKeysSet.delete(k);\n }\n\n if (bKeysSet.size) return false;\n }\n }\n\n return true;\n};\n","/* eslint-disable no-param-reassign */\n/* eslint-disable @typescript-eslint/no-use-before-define */\n/* eslint-disable complexity */\n/* eslint-disable no-bitwise */\n/* eslint-disable no-plusplus */\n/* eslint-disable prefer-arrow/prefer-arrow-functions */\n/* eslint-disable func-style */\n\n//\n// Credit to: https://github.com/gleam-lang/stdlib/blob/main/src/dict.mjs\n// Ported to typescript\n//\n\nconst referenceMap = new WeakMap<WeakKey, number>();\nconst tempDataView = new DataView(new ArrayBuffer(8));\nlet referenceUID = 0;\n\n/**\n * hash the object by reference using a weak map and incrementing uid\n */\nconst hashByReference = (o: WeakKey): number => {\n const known = referenceMap.get(o);\n if (known !== undefined) {\n return known;\n }\n const hash = referenceUID++;\n if (referenceUID === 0x7fffffff) {\n referenceUID = 0;\n }\n referenceMap.set(o, hash);\n return hash;\n};\n\n/**\n * merge two hashes in an order sensitive way\n */\nexport const hashMerge = (a: number, b: number): number => (a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2))) | 0;\n\n/**\n * standard string hash popularized by java\n */\nconst hashString = (s: string): number => {\n let hash = 0;\n const len = s.length;\n for (let i = 0; i < len; i++) {\n hash = (Math.imul(31, hash) + s.charCodeAt(i)) | 0;\n }\n return hash;\n};\n\n/**\n * hash a number by converting to two integers and do some jumbling\n */\nconst hashNumber = (n: number): number => {\n tempDataView.setFloat64(0, n);\n const i = tempDataView.getInt32(0);\n const j = tempDataView.getInt32(4);\n return Math.imul(0x45d9f3b, (i >> 16) ^ i) ^ j;\n};\n\n/**\n * hash a BigInt by converting it to a string and hashing that\n */\nconst hashBigInt = (n: bigint): number => hashString(n.toString());\n\n/**\n * hash any js object\n */\nconst hashObject = (o: object): number => {\n const proto = Object.getPrototypeOf(o) as { hashCode: (v: unknown) => unknown } | null;\n if (proto !== null && typeof proto.hashCode === 'function') {\n try {\n const code = (o as { hashCode: (v: unknown) => unknown }).hashCode(o);\n if (typeof code === 'number') {\n return code;\n }\n // eslint-disable-next-line no-empty\n } catch {}\n }\n if (o instanceof Promise || o instanceof WeakSet || o instanceof WeakMap) {\n return hashByReference(o);\n }\n if (o instanceof Date) {\n return hashNumber(o.getTime());\n }\n let h = 0;\n if (o instanceof ArrayBuffer) {\n o = new Uint8Array(o);\n }\n if (Array.isArray(o) || o instanceof Uint8Array) {\n for (let i = 0; i < o.length; i++) {\n h = (Math.imul(31, h) + getHash(o[i])) | 0;\n }\n } else if (o instanceof Set) {\n o.forEach(v => {\n h = (h + getHash(v)) | 0;\n });\n } else if (o instanceof Map) {\n o.forEach((v, k) => {\n h = (h + hashMerge(getHash(v), getHash(k))) | 0;\n });\n } else {\n const keys = Object.keys(o) as (keyof typeof o)[];\n for (let i = 0; i < keys.length; i++) {\n const k = keys[i];\n const v = o[k];\n h = (h + hashMerge(getHash(v), hashString(k))) | 0;\n }\n }\n return h;\n};\n\n/**\n * hash any js value\n */\nexport function getHash(u: unknown): number {\n if (u === null) return 0x42108422;\n if (u === undefined) return 0x42108423;\n if (u === true) return 0x42108421;\n if (u === false) return 0x42108420;\n switch (typeof u) {\n case 'number':\n return hashNumber(u);\n case 'string':\n return hashString(u);\n case 'bigint':\n return hashBigInt(u);\n case 'object':\n return hashObject(u);\n case 'symbol':\n return hashByReference(u);\n case 'function':\n return hashByReference(u);\n default:\n throw new Error('getHash - non-exhaustive switch statement');\n }\n}\n","/* eslint-disable no-param-reassign */\n/* eslint-disable @typescript-eslint/no-use-before-define */\n/* eslint-disable no-bitwise */\n/* eslint-disable no-plusplus */\n/* eslint-disable prefer-arrow/prefer-arrow-functions */\n/* eslint-disable func-style */\n\n//\n// This file is a fork of: https://github.com/gleam-lang/stdlib/blob/main/src/dict.mjs\n// * ported to typescript\n// * originally written with full immutability for Gleam, but I updated it to do in-place mutations for performance gains\n// * My HashMap and HashSet are intentionally mutable to match that characteristic of the native Map and Set\n//\n\nimport { isEqual } from '../../helpers/isEqual';\n\nimport { getHash } from './hashing';\n\nconst SHIFT = 5; // number of bits you need to shift by to get the next bucket\nconst BUCKET_SIZE = 2 ** SHIFT;\nconst MASK = BUCKET_SIZE - 1; // used to zero out all bits not in the bucket\nconst MAX_INDEX_NODE = BUCKET_SIZE / 2; // when does index node grow into array node\nconst MIN_ARRAY_NODE = BUCKET_SIZE / 4; // when does array node shrink to index node\nconst ENTRY = 0;\nconst ARRAY_NODE = 1;\nconst INDEX_NODE = 2;\nconst COLLISION_NODE = 3;\n\n/** @internal */\nexport type Node<K, V> = ArrayNode<K, V> | CollisionNode<K, V> | IndexNode<K, V>;\ntype Entry<K, V> = { k: K; type: typeof ENTRY; v: V };\ntype ArrayNode<K, V> = {\n array: (Entry<K, V> | Node<K, V> | undefined)[];\n size: number;\n type: typeof ARRAY_NODE;\n};\n/** @internal */\nexport type IndexNode<K, V> = { array: (Entry<K, V> | Node<K, V>)[]; bitmap: number; type: typeof INDEX_NODE };\ntype CollisionNode<K, V> = { array: Entry<K, V>[]; hash: number; type: typeof COLLISION_NODE };\ntype Flag = { val: boolean };\n\n/** @internal */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const createEmptyNode = (): IndexNode<any, any> => ({\n array: [],\n bitmap: 0,\n type: INDEX_NODE,\n});\n\n/**\n * Mask the hash to get only the bucket corresponding to shift\n * @internal\n */\nexport function mask(hash: number, shift: number): number {\n return (hash >>> shift) & MASK;\n}\n\n/**\n * Set only the Nth bit where N is the masked hash\n * @internal\n */\nexport function bitpos(hash: number, shift: number): number {\n return 1 << mask(hash, shift);\n}\n\n/**\n * Count the number of 1 bits in a number\n */\nfunction bitcount(x: number): number {\n x -= (x >> 1) & 0x55555555;\n x = (x & 0x33333333) + ((x >> 2) & 0x33333333);\n x = (x + (x >> 4)) & 0x0f0f0f0f;\n x += x >> 8;\n x += x >> 16;\n return x & 0x7f;\n}\n\n/**\n * Calculate the array index of an item in a bitmap index node\n */\nfunction index(bitmap: number, bit: number): number {\n return bitcount(bitmap & (bit - 1));\n}\n\n/**\n * Create a new node containing two entries\n */\nfunction createNode<K, V>(shift: number, key1: K, val1: V, key2hash: number, key2: K, val2: V): Node<K, V> {\n const key1hash = getHash(key1);\n if (key1hash === key2hash) {\n return {\n array: [\n { k: key1, type: ENTRY, v: val1 },\n { k: key2, type: ENTRY, v: val2 },\n ],\n hash: key1hash,\n type: COLLISION_NODE,\n };\n }\n const addedLeaf = { val: false };\n return assoc(\n assocIndex(createEmptyNode(), shift, key1hash, key1, val1, addedLeaf),\n shift,\n key2hash,\n key2,\n val2,\n addedLeaf,\n );\n}\n\n/**\n * Associate a node with a new entry, creating a new node\n * @internal\n */\nexport function assoc<K, V>(\n root: Node<K, V>,\n shift: number,\n hash: number,\n key: K,\n val: V,\n addedLeaf: Flag,\n): Node<K, V> {\n switch (root.type) {\n case ARRAY_NODE:\n return assocArray(root, shift, hash, key, val, addedLeaf);\n case INDEX_NODE:\n return assocIndex(root, shift, hash, key, val, addedLeaf);\n case COLLISION_NODE:\n return assocCollision(root, shift, hash, key, val, addedLeaf);\n default:\n throw new Error('function assoc :: non-exhaustive');\n }\n}\n\nfunction assocArray<K, V>(\n root: ArrayNode<K, V>,\n shift: number,\n hash: number,\n key: K,\n val: V,\n addedLeaf: Flag,\n): Node<K, V> {\n const idx = mask(hash, shift);\n const node = root.array[idx];\n\n // if the corresponding index is empty set the index to a newly created node\n if (node === undefined) {\n addedLeaf.val = true;\n root.array[idx] = { k: key, type: ENTRY, v: val };\n root.size += 1;\n return root;\n }\n\n if (node.type === ENTRY) {\n // if keys are equal replace the entry\n if (isEqual(key, node.k)) {\n if (val === node.v) return root;\n root.array[idx] = { k: key, type: ENTRY, v: val };\n return root;\n }\n\n // otherwise upgrade the entry to a node and insert\n addedLeaf.val = true;\n root.array[idx] = createNode(shift + SHIFT, node.k, node.v, hash, key, val);\n return root;\n }\n\n // otherwise call assoc on the child node\n root.array[idx] = assoc(node, shift + SHIFT, hash, key, val, addedLeaf);\n return root;\n}\n\nfunction assocIndex<K, V>(\n root: IndexNode<K, V>,\n shift: number,\n hash: number,\n key: K,\n val: V,\n addedLeaf: Flag,\n): Node<K, V> {\n const bit = bitpos(hash, shift);\n const idx = index(root.bitmap, bit);\n // if there is already a item at this hash index..\n if ((root.bitmap & bit) !== 0) {\n // if there is a node at the index (not an entry), call assoc on the child node\n const node = root.array[idx];\n if (node.type !== ENTRY) {\n const n = assoc(node, shift + SHIFT, hash, key, val, addedLeaf);\n root.array[idx] = n;\n return root;\n }\n\n // otherwise there is an entry at the index\n // if the keys are equal replace the entry with the updated value\n const nodeKey = node.k;\n if (isEqual(key, nodeKey)) {\n if (val === node.v) return root;\n node.v = val;\n return root;\n }\n\n // if the keys are not equal, replace the entry with a new child node\n addedLeaf.val = true;\n root.array[idx] = createNode(shift + SHIFT, nodeKey, node.v, hash, key, val);\n root.type = INDEX_NODE;\n return root;\n }\n // else there is currently no item at the hash index\n const n = root.array.length;\n // if the number of nodes is at the maximum, expand this node into an array node\n if (n >= MAX_INDEX_NODE) {\n // create a 32 length array for the new array node (one for each bit in the hash)\n const nodes = new Array<Entry<K, V> | Node<K, V>>(32);\n // create and insert a node for the new entry\n const jdx = mask(hash, shift);\n nodes[jdx] = assocIndex(createEmptyNode(), shift + SHIFT, hash, key, val, addedLeaf);\n let j = 0;\n let { bitmap } = root;\n // place each item in the index node into the correct spot in the array node\n // loop through all 32 bits / array positions\n for (let i = 0; i < 32; i++) {\n if ((bitmap & 1) !== 0) {\n const node = root.array[j++];\n nodes[i] = node;\n }\n // shift the bitmap to process the next bit\n bitmap >>>= 1;\n }\n return {\n array: nodes,\n size: n + 1,\n type: ARRAY_NODE,\n };\n }\n\n // else there is still space in this index node\n // simply insert a new entry at the hash index\n\n addedLeaf.val = true;\n\n const newEntryNode: Entry<K, V> = {\n k: key,\n type: ENTRY,\n v: val,\n };\n root.array.splice(idx, 0, newEntryNode);\n root.bitmap |= bit;\n return root;\n}\n\nfunction assocCollision<K, V>(\n root: CollisionNode<K, V>,\n shift: number,\n hash: number,\n key: K,\n val: V,\n addedLeaf: Flag,\n): Node<K, V> {\n // if there is a hash collision\n if (hash === root.hash) {\n const idx = collisionIndexOf(root, key);\n // if this key already exists replace the entry with the new value\n if (idx !== -1) {\n const entry = root.array[idx];\n\n if (entry.v === val) return root;\n\n root.array[idx] = { k: key, type: ENTRY, v: val };\n return root;\n }\n\n // otherwise insert the entry at the end of the array\n addedLeaf.val = true;\n root.array.push({ k: key, type: ENTRY, v: val });\n return root;\n }\n\n // if there is no hash collision, upgrade to an index node\n return assoc(\n {\n array: [root],\n bitmap: bitpos(root.hash, shift),\n type: INDEX_NODE,\n },\n shift,\n hash,\n key,\n val,\n addedLeaf,\n );\n}\n\n/**\n * Find the index of a key in the collision node's array\n */\nfunction collisionIndexOf<K, V>(root: CollisionNode<K, V>, key: K): number {\n const size = root.array.length;\n for (let i = 0; i < size; i++) {\n if (isEqual(key, root.array[i].k)) {\n return i;\n }\n }\n return -1;\n}\n\n/**\n * Return the found entry or undefined if not present in the root\n * @internal\n */\nexport function find<K, V>(root: Node<K, V>, shift: number, hash: number, key: K): Entry<K, V> | undefined {\n switch (root.type) {\n case ARRAY_NODE:\n return findArray(root, shift, hash, key);\n case INDEX_NODE:\n return findIndex(root, shift, hash, key);\n case COLLISION_NODE:\n return findCollision(root, key);\n default:\n throw new Error('function find :: non-exhaustive');\n }\n}\n\nfunction findArray<K, V>(root: ArrayNode<K, V>, shift: number, hash: number, key: K): Entry<K, V> | undefined {\n const idx = mask(hash, shift);\n const node = root.array[idx];\n if (node === undefined) {\n return undefined;\n }\n if (node.type !== ENTRY) {\n return find(node, shift + SHIFT, hash, key);\n }\n if (isEqual(key, node.k)) {\n return node;\n }\n return undefined;\n}\n\nfunction findIndex<K, V>(root: IndexNode<K, V>, shift: number, hash: number, key: K): Entry<K, V> | undefined {\n const bit = bitpos(hash, shift);\n if ((root.bitmap & bit) === 0) {\n return undefined;\n }\n const idx = index(root.bitmap, bit);\n const node = root.array[idx];\n if (node.type !== ENTRY) {\n return find(node, shift + SHIFT, hash, key);\n }\n if (isEqual(key, node.k)) {\n return node;\n }\n return undefined;\n}\n\nfunction findCollision<K, V>(root: CollisionNode<K, V>, key: K): Entry<K, V> | undefined {\n const idx = collisionIndexOf(root, key);\n if (idx < 0) {\n return undefined;\n }\n return root.array[idx];\n}\n\n/**\n * Remove an entry from the root, returning the updated root.\n * Returns undefined if the node should be removed from the parent.\n * @internal\n */\nexport function without<K, V>(root: Node<K, V>, shift: number, hash: number, key: K): Node<K, V> | undefined {\n switch (root.type) {\n case ARRAY_NODE:\n return withoutArray(root, shift, hash, key);\n case INDEX_NODE:\n return withoutIndex(root, shift, hash, key);\n case COLLISION_NODE:\n return withoutCollision(root, key);\n default:\n throw new Error('function without :: non-exhaustive');\n }\n}\n\nfunction withoutArray<K, V>(root: ArrayNode<K, V>, shift: number, hash: number, key: K): Node<K, V> | undefined {\n const idx = mask(hash, shift);\n const node = root.array[idx];\n if (node === undefined) return root; // already empty\n\n let n: Node<K, V> | undefined;\n // if node is an entry and the keys are not equal there is nothing to remove\n // if node is not an entry do a recursive call\n if (node.type === ENTRY) {\n if (!isEqual(node.k, key)) {\n return root; // no changes\n }\n } else {\n n = without(node, shift + SHIFT, hash, key);\n }\n // if ENTRY and isEqual, or the recursive call returned undefined, the node should be removed\n if (n === undefined) {\n // if the number of child nodes is at the minimum, pack into an index node\n if (root.size <= MIN_ARRAY_NODE) {\n const arr = root.array;\n const out = new Array<Entry<K, V> | Node<K, V>>(root.size - 1);\n let i = 0;\n let j = 0;\n let bitmap = 0;\n while (i < idx) {\n const nv = arr[i];\n if (nv !== undefined) {\n out[j] = nv;\n bitmap |= 1 << i;\n ++j;\n }\n ++i;\n }\n ++i; // skip copying the removed node\n while (i < arr.length) {\n const nv = arr[i];\n if (nv !== undefined) {\n out[j] = nv;\n bitmap |= 1 << i;\n ++j;\n }\n ++i;\n }\n return {\n array: out,\n bitmap,\n type: INDEX_NODE,\n };\n }\n\n root.array[idx] = n;\n root.size -= 1;\n return root;\n }\n\n root.array[idx] = n;\n return root;\n}\n\nfunction withoutIndex<K, V>(root: IndexNode<K, V>, shift: number, hash: number, key: K): Node<K, V> | undefined {\n const bit = bitpos(hash, shift);\n if ((root.bitmap & bit) === 0) return root; // already empty\n\n const idx = index(root.bitmap, bit);\n const node = root.array[idx];\n\n // if the item is not an entry\n if (node.type !== ENTRY) {\n const n = without(node, shift + SHIFT, hash, key);\n\n // if not undefined, the child node still has items, so update it\n if (n !== undefined) {\n root.array[idx] = n;\n return root;\n }\n\n // otherwise the child node should be removed\n // if it was the only child node, remove this node from the parent\n if (root.bitmap === bit) return undefined;\n // otherwise just remove the child node\n root.array.splice(idx, 1);\n root.bitmap ^= bit;\n return root;\n }\n\n // otherwise the item is an entry, remove it if the key matches\n if (isEqual(key, node.k)) {\n // if it was the only child node, remove this node from the parent\n if (root.bitmap === bit) return undefined;\n\n root.array.splice(idx, 1);\n root.bitmap ^= bit;\n return root;\n }\n\n return root;\n}\n\nfunction withoutCollision<K, V>(root: CollisionNode<K, V>, key: K): Node<K, V> | undefined {\n const idx = collisionIndexOf(root, key);\n // if the key not found, no changes\n if (idx < 0) return root;\n\n // otherwise the entry was found, remove it\n // if it was the only entry in this node, remove the whole node\n if (root.array.length === 1) return undefined;\n\n // otherwise just remove the entry\n root.array.splice(idx, 1);\n return root;\n}\n\n/** @internal */\nexport function forEach<K, V>(root: Node<K, V> | undefined, fn: (value: V, key: K) => void): void {\n if (root === undefined) {\n return;\n }\n const items = root.array;\n const size = items.length;\n for (let i = 0; i < size; i++) {\n const item = items[i];\n if (item === undefined) {\n continue;\n }\n if (item.type === ENTRY) {\n fn(item.v, item.k);\n continue;\n }\n forEach(item, fn);\n }\n}\n\n/** @internal */\nexport function toArray<K, V>(root: Node<K, V> | undefined): [K, V][] {\n const array: [K, V][] = [];\n forEach(root, (v, k) => array.push([k, v]));\n return array;\n}\n","/* eslint-disable no-underscore-dangle */\n/* eslint-disable @typescript-eslint/unified-signatures */\n/* eslint-disable @typescript-eslint/prefer-return-this-type */\n/* eslint-disable no-bitwise */\n/* eslint-disable no-plusplus */\n\n//\n// Inspired by: https://github.com/gleam-lang/stdlib/blob/main/src/dict.mjs\n// Ported to typescript\n//\n\nimport { isEqual } from '../helpers/isEqual';\n\nimport { getHash, hashMerge } from './internal/hashing';\nimport type { Node } from './internal/hashTree';\nimport { assoc, createEmptyNode, find, forEach, toArray, without } from './internal/hashTree';\n\n/**\n * ### HashMap\n *\n * Key equality is determined by `isEqual`\n *\n * If your keys are Javascript [primitives](https://developer.mozilla.org/en-US/docs/Glossary/Primitive), there is no benefit in using a HashMap over the native [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map).\n *\n * #### Construction\n * `HashMap` is a newable class, and has the static `HashMap.from` functional constructor for convenience\n * * In addition to the arguments the new constructor accepts, `HashMap.from()` also accepts objects directly\n *\n * #### Native Map API\n * The HashMap API fully implements the Map API and can act as a drop-in replacement with a few caveats:\n * * Non-primitive Map keys are equal by reference, a Map may contain two different keys that share the same structure. A HashMap will not see those keys as being different.\n * * Order of insertion is not retained.\n *\n * Those methods are:\n * * `clear`, `delete`, `entries`, `forEach`, `get`, `has`, `keys`, `set`, `values`, `[Symbol.Iterator]`\n * * static `groupBy`\n * * readonly prop `size`\n *\n * #### Array API\n * HashMap partially implements the Array API, specifically the reduction methods that can apply.\n * The callbackfn signatures match their array equivalent with `k: Key` replacing the `index: number` argument.\n *\n * Those methods are:\n * * `map`, `filter`, `find`, `reduce`, `some`, `every`\n * * Notes:\n * * `map` and `filter` are immutable, returning a new instance of HashMap\n * * `find` returns a tuple `[K, V] | undefined`\n *\n * #### Additional Utility APIs\n * * `clone` - will return a new instance of a HashMap with the exact same key/value pair entries\n * * `equals` - determine if another HashMap is equal to `this` by determining they share the same key/value pair entries\n * * Therefor `hashMap.equals(hashMap.clone())` will always return `true`\n *\n * #### Custom Equality and Hashing\n * Internally, class instances who's prototypes implement `equals: (other: typeof this) => boolean` and `hashCode(self: typeof this) => number`\n * will be used to determine equality between instances and an instance's hash value respectively.\n * It is recommended to implement these on any class where equality cannot be determined testing on public properties only\n *\n * @category Structures\n */\nexport class HashMap<K, V> implements Iterable<[K, V]> {\n private root: Node<K, V> | undefined;\n private _size: number;\n\n //#region Constructors\n\n /**\n * Create a HashMap from a Map.\n *\n * Note: If your Map has non-primitive keys that do not equal by reference but deep-equal by structure, only they last key/value pair will retain in the HashMap.\n *\n * @group Constructors\n */\n static from<K, V>(map: Map<K, V>): HashMap<K, V>;\n /**\n * Create a HashMap from an Array of Entries.\n *\n * Note: If any `key` in `[key, value]` is a non-primitive and their are multiples that do not equal by reference but deep-equal by structure, only they last key/value pair will retain in the HashMap.\n *\n * @group Constructors\n */\n static from<K, V>(entries: readonly [K, V][]): HashMap<K, V>;\n /**\n * Create a HashMap from an Iterable of Entries.\n *\n * Note: If any `key` in `[key, value]` is a non-primitive and their are multiples that do not equal by reference but deep-equal by structure, only they last key/value pair will retain in the HashMap.\n *\n * @group Constructors\n */\n static from<K, V>(iterable: Iterable<readonly [K, V]>): HashMap<K, V>;\n /**\n * Create a HashMap from an Object.\n *\n * Note: key/value pairs are created from the result of passing the argument to `Object.entries`.\n *\n * @group Constructors\n */\n static from<V>(object: Record<PropertyKey, V>): HashMap<string, V>;\n static from<K, V>(oneOfThem?: Iterable<readonly [K, V]> | Record<string, V>): HashMap<K, V> {\n if (oneOfThem == null) {\n return new HashMap<K, V>();\n }\n\n if (Symbol.iterator in oneOfThem) {\n return new HashMap<K, V>(oneOfThem);\n }\n\n // else isObject\n return new HashMap<K, V>(Object.entries(oneOfThem) as [K, V][]);\n }\n\n /**\n * Create an empty HashMap.\n */\n constructor();\n /**\n * Create a HashMap from an Array of Entries.\n *\n * Note: If any `key` in `[key, value]` is a non-primitive and their are multiples that do not equal by reference but deep-equal by structure, only they last key/value pair will retain in the HashMap.\n */\n constructor(entries?: readonly (readonly [K, V])[] | null);\n /**\n * Create a HashMap from an Iterable of Entries.\n *\n * Note: If any `key` in `[key, value]` is a non-primitive and their are multiples that do not equal by reference but deep-equal by structure, only they last key/value pair will retain in the HashMap.\n */\n constructor(iterable?: Iterable<readonly [K, V]> | null);\n constructor(iterable?: Iterable<readonly [K, V]> | null) {\n this.root = undefined;\n this._size = 0;\n if (iterable != null) {\n for (const [k, v] of iterable) {\n this.set(k, v);\n }\n }\n }\n\n //#endregion\n\n //#region Utility\n\n /**\n * Groups members of an iterable according to the return value of the passed callback.\n * @group Utility\n * @param items An iterable.\n * @param keySelector A callback which will be invoked for each item in items.\n */\n static groupBy<K, V>(items: Iterable<V>, keySelector: (item: V, index: number) => K): HashMap<K, V[]> {\n const dict = new HashMap<K, V[]>();\n let i = 0;\n for (const val of items) {\n const key = keySelector(val, i);\n if (!dict.has(key)) {\n dict.set(key, []);\n }\n dict.get(key)!.push(val);\n ++i;\n }\n return dict;\n }\n\n /**\n * @group Utility\n * @returns an immutable copy of the HashMap\n */\n clone(): HashMap<K, V> {\n const clone = new HashMap<K, V>();\n clone.root = structuredClone(this.root);\n clone._size = this.size;\n return clone;\n }\n\n /**\n * Check if this HashMap is equal to another\n * Returns `true` when\n * * referentially equal\n * * both HashMaps contain exactly the same key/value pairs\n * * both are empty\n *\n * @group Utility\n * @param other another HashMap\n * @returns boolean indicating whether the other HashMap has the exactly same entries as this\n */\n equals(other: HashMap<K, V>): boolean {\n if (this === other) return true;\n if (!(other instanceof HashMap) || this._size !== other._size) return false;\n\n for (const [k, v] of this) {\n if (!isEqual(other.get(k), v)) {\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * Used internally by `getHash()`\n * @group Utility\n * @returns the hash of this HashMap\n */\n private hashCode(): number {\n let h = 0;\n this.forEach((v, k) => {\n h = (h + hashMerge(getHash(v), getHash(k))) | 0;\n });\n return h;\n }\n\n //#endregion\n\n //#region Entries\n\n /**\n * @group Entries\n * @returns the number of elements in the HashMap.\n */\n get size(): number {\n return this._size;\n }\n\n /**\n * Empties the HashMap, clearing out all entries.\n * @group Entries\n */\n clear(): void {\n this.root = undefined;\n this._size = 0;\n }\n\n /**\n * @group Entries\n * @returns true if an element in the HashMap existed and has been removed, or false if the element does not exist.\n */\n delete(key: K): boolean {\n if (this.root === undefined) return false;\n if (!this.has(key)) return false;\n\n this.root = without(this.root, 0, getHash(key), key);\n this._size -= 1;\n\n return true;\n }\n\n /**\n * Executes a provided function once per each key/value pair in the Map, in insertion order.\n * @group Entries\n */\n forEach(fn: (val: V, key: K) => void) {\n forEach(this.root, fn);\n }\n\n /**\n * Returns a specified element from the HashMap. If the value that is associated to the provided key is an object, then you will get a reference to that object and any change made to that object will effectively modify it inside the HashMap.\n * @group Entries\n * @returns Returns the element associated with the specified key. If no element is associated with the specified key, undefined is returned.\n */\n get(key: K): V | undefined {\n if (this.root === undefined) return undefined;\n const found = find(this.root, 0, getHash(key), key);\n return found?.v;\n }\n\n /**\n * @group Entries\n * @returns boolean indicating whether an element with the specified key exists or not.\n */\n has(key: K): boolean {\n if (this.root === undefined) return false;\n return find(this.root, 0, getHash(key), key) !== undefined;\n }\n\n /**\n * Adds a new element with a specified key and value to the HashMap. If an element with the same key already exists, the element will be updated.\n * @group Entries\n */\n set(key: K, val: V): HashMap<K, V> {\n const addedLeaf = { val: false };\n const root = this.root ?? createEmptyNode();\n this.root = assoc(root, 0, getHash(key), key, val, addedLeaf);\n this._size = addedLeaf.val ? this._size + 1 : this._size;\n return this;\n }\n\n //#endregion\n\n //#region Iterables\n\n /**\n * Returns an iterable of entries in the HashMap.\n * @group Iterables\n */\n [Symbol.iterator]() {\n return this.entries();\n }\n\n /**\n * Returns an iterable of key, value pairs for every entry in the HashMap.\n * @group Iterables\n */\n entries() {\n return Iterator.from(toArray(this.root));\n }\n\n /**\n * Returns an iterable of keys in the HashMap.\n * @group Iterables\n */\n keys() {\n return this.entries().map(([k]) => k);\n }\n\n /**\n * Returns an iterable of values in the HashMap.\n * @group Iterables\n */\n values() {\n return this.entries().map(([, v]) => v);\n }\n\n //#endregion\n\n //#region Reductions\n\n /**\n * Calls a defined callback function on each entry of a HashMap, and returns a HashMap with the same keys as the original with those values mapped to the result\n *\n * @group Reductions\n * @param callbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each entry in the HashMap.\n * @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.\n */\n map<U, C>(callbackfn: (value: V, key: K, hashMap: HashMap<K, V>) => U, thisArg: C): HashMap<K, U>;\n /**\n * Calls a defined callback function on each entry of a HashMap, and returns a HashMap with the same keys as the original with those values mapped to the result\n *\n * @group Reductions\n * @param callbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each entry in the HashMap.\n */\n map<U>(callbackfn: (value: V, key: K, hashMap: HashMap<K, V>) => U): HashMap<K, U>;\n map<U>(callbackfn: (value: V, key: K, hashMap: HashMap<K, V>) => U, thisArg?: unknown): HashMap<K, U> {\n const hashMap = new HashMap<K, U>();\n this.forEach((v, k) => {\n hashMap.set(k, callbackfn.call(thisArg, v, k, this));\n });\n return hashMap;\n }\n\n /**\n * Returns a new HashMap with only the entries that meet the condition specified in a callback function.\n *\n * @group Reductions\n * @param predicate A function that accepts up to three arguments. The filter method calls the predicate function one time for each entry in the HashMap.\n * @param thisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value.\n */\n filter<S extends V, C>(\n predicate: (value: V, key: K, hashMap: HashMap<K, V>) => value is S,\n thisArg: C,\n ): HashMap<K, S>;\n /**\n * Returns a new HashMap with only the entries that meet the condition specified in a callback function.\n *\n * @group Reductions\n * @param predicate A function that accepts up to three arguments. The filter method calls the predicate function one time for each entry in the HashMap.\n */\n filter<S extends V>(predicate: (value: V, key: K, hashMap: HashMap<K, V>) => value is S): HashMap<K, S>;\n /**\n * Returns a new HashMap with only the entries that meet the condition specified in a callback function.\n *\n * @group Reductions\n * @param predicate A function that accepts up to three arguments. The filter method calls the predicate function one time for each entry in the HashMap.\n * @param thisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value.\n */\n filter<C>(predicate: (value: V, key: K, hashMap: HashMap<K, V>) => boolean, thisArg: C): HashMap<K, V>;\n /**\n * Returns a new HashMap with only the entries that meet the condition specified in a callback function.\n *\n * @group Reductions\n * @param predicate A function that accepts up to three arguments. The filter method calls the predicate function one time for each entry in the HashMap.\n */\n filter(predicate: (value: V, key: K, hashMap: HashMap<K, V>) => boolean): HashMap<K, V>;\n filter<C>(predicate: (value: V, key: K, hashMap: HashMap<K, V>) => boolean, thisArg?: C): HashMap<K, V> {\n const hashMap = new HashMap<K, V>();\n this.forEach((v, k) => {\n if (predicate.call(thisArg, v, k, this)) {\n hashMap.set(k, v);\n }\n });\n return hashMap;\n }\n\n /**\n * Returns the key/value tuple of the first entry in the HashMap where predicate is true, or undefined\n * otherwise.\n *\n * @group Reductions\n * @param predicate find calls predicate once for each entry of the HashMap,\n * until it finds one where predicate returns true. If such an entry is found, find\n * immediately returns that entry. Otherwise, find returns undefined.\n * @param thisArg If provided, it will be used as the this value for each invocation of\n * predicate. If it is not provided, undefined is used instead.\n */\n find<S extends V, C>(\n predicate: (value: V, key: K, hashMap: HashMap<K, V>) => value is S,\n thisArg: C,\n ): [K, S] | undefined;\n /**\n * Returns the key/value tuple of the first entry in the HashMap where predicate is true, or undefined\n * otherwise.\n *\n * @group Reductions\n * @param predicate find calls predicate once for each entry of the HashMap,\n * until it finds one where predicate returns true. If such an entry is found, find\n * immediately returns that entry. Otherwise, find returns undefined.\n */\n find<S extends V>(predicate: (value: V, key: K, hashMap: HashMap<K, V>) => value is S): [K, S] | undefined;\n /**\n * Returns the key/value tuple of the first entry in the HashMap where predicate is true, or undefined\n * otherwise.\n *\n * @group Reductions\n * @param predicate find calls predicate once for each entry of the HashMap,\n * until it finds one where predicate returns true. If such an entry is found, find\n * immediately returns that entry. Otherwise, find returns undefined.\n * @param thisArg If provided, it will be used as the this value for each invocation of\n * predicate. If it is not provided, undefined is used instead.\n */\n find<C>(predicate: (value: V, key: K, hashMap: HashMap<K, V>) => boolean, thisArg: C): [K, V] | undefined;\n /**\n * Returns the key/value tuple of the first entry in the HashMap where predicate is true, or undefined\n * otherwise.\n *\n * @group Reductions\n * @param predicate find calls predicate once for each entry of the HashMap,\n * until it finds one where predicate returns true. If such an entry is found, find\n * immediately returns that entry. Otherwise, find returns undefined.\n */\n find(predicate: (value: V, key: K, hashMap: HashMap<K, V>) => boolean): [K, V] | undefined;\n find(predicate: (value: V, key: K, hashMap: HashMap<K, V>) => boolean, thisArg?: unknown): [K, V] | undefined {\n for (const [k, v] of this.entries()) {\n if (predicate.call(thisArg, v, k, this)) {\n return [k, v];\n }\n }\n return undefined;\n }\n\n /**\n * Calls the specified callback function for all the entries in the HashMap. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.\n * @group Reductions\n * @param callbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each entry int he HashMap.\n */\n reduce(callbackfn: (accumulator: V, value: V, key: K, hashMap: HashMap<K, V>) => V): V;\n /**\n * Calls the specified callback function for all the entries in the HashMap. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.\n * @group Reductions\n * @param callbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each entry in the HashMap.\n * @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead.\n */\n reduce<U>(callbackfn: (accumulator: U, value: V, key: K, hashMap: HashMap<K, V>) => U, initialValue: U): U;\n reduce<U>(callbackfn: (accumulator: U, value: V, key: K, hashMap: HashMap<K, V>) => U, initialValue?: U): U {\n if (arguments.length === 1 && this.size === 0) {\n throw new TypeError('Reduce of empty HashMap with no initial value');\n }\n\n let entries = Array.from(this.entries());\n let acc: U;\n if (arguments.length === 1) {\n const [head, ...rest] = entries;\n acc = head[1] as unknown as U;\n entries = rest;\n } else {\n acc = initialValue!;\n }\n\n for (const [k, v] of entries) {\n acc = callbackfn(acc, v, k, this);\n }\n\n return acc;\n }\n\n /**\n * Determines whether the specified callback function returns true for any entry in the HashMap\n *\n * @group Reductions\n * @param predicate A function that accepts up to three arguments. The some method calls\n * the predicate function for each entry in the HashMap until the predicate returns a value\n * which is coercible to the Boolean value true, or until the end of the HashMap iteration.\n * @param thisArg An object to which the this keyword can refer in the predicate function.\n * If thisArg is omitted, undefined is used as the this value.\n */\n some<C>(predicate: (this: C, value: V, key: K, hashMap: HashMap<K, V>) => boolean, thisArg: C): boolean;\n /**\n * Determines whether the specified callback function returns true for any entry in the HashMap\n *\n * @group Reductions\n * @param predicate A function that accepts up to three arguments. The some method calls\n * the predicate function for each entry in the HashMap until the predicate returns a value\n * which is coercible to the Boolean value true, or until the end of the HashMap iteration.\n */\n some(predicate: (value: V, key: K, hashMap: HashMap<K, V>) => boolean): boolean;\n some(predicate: (value: V, key: K, hashMap: HashMap<K, V>) => boolean, thisArg?: unknown): boolean {\n for (const [k, v] of this.entries()) {\n if (predicate.call(thisArg, v, k, this)) return true;\n }\n return false;\n }\n\n /**\n * Determines whether all the entries of a HashMap satisfy the specified test.\n *\n * @group Reductions\n * @param predicate A function that accepts up to three arguments. The every method calls\n * the predicate function for each entries in the HashMap until the predicate returns a value\n * which is coercible to the Boolean value false, or until the end of the HashMap iteration.\n * @param thisArg An object to which the this keyword can refer in the predicate function.\n * If thisArg is omitted, undefined is used as the this value.\n */\n every<S extends V, C>(\n predicate: (value: V, key: K, hashMap: HashMap<K, V>) => value is S,\n thisArg: C,\n ): this is HashMap<K, S>;\n /**\n * Determines whether all the entries of a HashMap satisfy the specified test.\n *\n * @group Reductions\n * @param predicate A function that accepts up to three arguments. The every method calls\n * the predicate function for each entries in the HashMap until the predicate returns a value\n * which is coercible to the Boolean value false, or until the end of the HashMap iteration.\n * @param thisArg An object to which the this keyword can refer in the predicate function.\n * If thisArg is omitted, undefined is used as the this value.\n */\n every(predicate: (value: V, key: K, hashMap: HashMap<K, V>) => boolean, thisArg: unknown): boolean;\n /**\n * Determines whether all the entries of a HashMap satisfy the specified test.\n *\n * @group Reductions\n * @param predicate A function that accepts up to three arguments. The every method calls\n * the predicate function for each element in the array until the predicate returns a value\n * which is coercible to the Boolean value false, or until the end of the HashMap iteration.\n */\n every<S extends V>(predicate: (value: V, key: K, hashMap: HashMap<K, V>) => value is S): this is HashMap<K, S>;\n every(predicate: (value: V, key: K, hashMap: HashMap<K, V>) => boolean): boolean;\n /**\n * Determines whether all the entries of a HashMap satisfy the specified test.\n *\n * @group Reductions\n * @param predicate A function that accepts up to three arguments. The every method calls\n * the predicate function for each element in the array until the predicate returns a value\n * which is coercible to the Boolean value false, or until the end of the HashMap iteration.\n */\n every(predicate: (value: V, key: K, hashMap: HashMap<K, V>) => boolean, thisArg?: unknown): boolean {\n for (const [k, v] of this.entries()) {\n if (!predicate.call(thisArg, v, k, this)) return false;\n }\n return true;\n }\n\n //#endregion\n}\n","/* eslint-disable no-bitwise */\n// get parent index (intDiv(i, 2))\nconst parent = (i: number) => ((i + 1) >>> 1) - 1;\n// double + 1\nconst left = (i: number) => (i << 1) + 1;\n// double + 2\nconst right = (i: number) => (i + 1) << 1;\n/* eslint-enable no-bitwise */\n\n/**\n *\n * @category Structures\n */\nexport class PriorityQueue<T> {\n private heap: T[] = [];\n\n constructor(\n private comparator: (a: T, b: T) => boolean,\n private equals: (a: T, b: T) => boolean = (a, b) => a === b,\n ) {}\n\n size() {\n return this.heap.length;\n }\n\n isEmpty() {\n return this.size() === 0;\n }\n\n peek() {\n return this.heap[0];\n }\n\n has(other: T): boolean {\n return this.heap.find(x => this.equals(x, other)) != null;\n }\n\n replace(value: T) {\n const replacedValue = this.peek();\n this.heap[0] = value;\n this.siftDown();\n return replacedValue;\n }\n\n push(value: T): void {\n this.heap.push(value);\n this.siftUp();\n }\n\n pop(): T | undefined {\n const poppedValue = this.peek();\n const bottom = this.size() - 1;\n if (bottom > 0) {\n this.swap(0, bottom);\n }\n this.heap.pop();\n this.siftDown();\n return poppedValue;\n }\n\n reorder(): void {\n const l = this.heap.length;\n const original = [...this.heap];\n this.heap = [];\n for (let i = 0; i < l; i += 1) {\n this.push(original[i]);\n }\n }\n\n toArray(): T[] {\n const copy = [...this.heap];\n const arr: T[] = [];\n while (this.size() > 0) {\n arr.push(this.pop()!);\n }\n this.heap = copy;\n return arr;\n }\n\n private greater(i: number, j: number) {\n return this.comparator(this.heap[i], this.heap[j]);\n }\n\n private swap = (i: number, j: number) => {\n const a = this.heap[i];\n const b = this.heap[j];\n this.heap[j] = a;\n this.heap[i] = b;\n };\n\n private siftUp() {\n let i = this.size() - 1;\n while (i > 0 && this.greater(i, parent(i))) {\n this.swap(i, parent(i));\n i = parent(i);\n }\n }\n\n private siftDown() {\n let i = 0;\n while (\n (left(i) < this.size() && this.greater(left(i), i)) ||\n (right(i) < this.size() && this.greater(right(i), i))\n ) {\n const maxChild = right(i) < this.size() && this.greater(right(i), left(i)) ? right(i) : left(i);\n this.swap(i, maxChild);\n i = maxChild;\n }\n }\n}\n","import type { HashMap } from '../../structures/hashMap';\n\n/** @internal */\nexport const createPath = <T>(prevMap: HashMap<T, T>, final: T): T[] => {\n const path: T[] = [final];\n let prev = prevMap.get(final);\n while (prev != null) {\n path.unshift(prev);\n prev = prevMap.get(prev);\n }\n return path;\n};\n","import { isEqual } from '../helpers/isEqual';\nimport { HashMap } from '../structures/hashMap';\nimport { PriorityQueue } from '../structures/priorityQueue';\n\nimport { createPath } from './internal/createPath';\n\n/**\n * Generator function that lazily iterates through each visit of an A* search.\n * If you want just the found path and totalCost to the solution, use `aStarAssoc`\n *\n * Each yield is an object `{ cost: number; path: T[] }`\n *\n * Notes:\n * * The first yield will be the initialState with a cost of 0\n * * Specific states may be visited multiple time, but through different costs and paths\n * * If the solved state is found, that will be the final yield, otherwise the final yield will happen once all possible states are visited\n * * Generator