{"version":3,"file":"index.mjs","sources":["../src/FuzzyTrieClass.ts","../src/index.ts"],"sourcesContent":["/**\n * A trie data structure optimized for approximate string matching,\n * allowing for flexible yet controlled searches based on lexicographical\n * distance.\n *\n * This class provides methods to add items with associated search strings and\n * to perform searches within a specified maximum Levenshtein distance.\n *\n * Levenshtein distance is a metric used in computer science and information\n * theory to measure the difference between two sequences of characters.\n * It's also known as the edit distance.\n *\n * Specifically, the Levenshtein distance between two words is the minimum\n * number of single-character edits (insertions, deletions, or substitutions)\n * required to change one word into the other.\n *\n * For example:\n *\n * The Levenshtein distance between \"kitten\" and \"sitting\" is 3:\n *\n * kitten → sitten (substitution of \"s\" for \"k\")\n * sitten → sittin (substitution of \"i\" for \"e\")\n * sittin → sitting (insertion of \"g\" at the end)\n * The Levenshtein distance between \"book\" and \"back\" is 2:\n *\n * book → bock (substitution of \"c\" for \"o\")\n * bock → back (substitution of \"a\" for \"o\")\n *\n * Using Levenshtein distance in this class allows the search to return\n * results that are close matches to the search term, even if they're\n * not exact matches. The results are then sorted based on this distance,\n * with the closest matches (lowest Levenshtein distance) appearing first\n * in the results array.\n *\n * This approach is particularly useful for implementing features like\n * autocorrect, spell-checking, or fuzzy search functionality where you\n * want to find close matches to a given input string.\n *\n * ------------------------------------------------------------------------\n *\n * Usage:\n * 1. Create an instance of FuzzyTrie:\n *    const trie = new FuzzyTrie<YourItemType>();\n *\n * 2. Add items to the trie:\n *    trie.addItemToTrie(\"search string\", yourItem);\n *    You can add multiple items with the same or different search strings.\n *\n * 3. Perform a fuzzy search:\n *    const results = trie.searchItems(\"query string\", maxAllowedDistance);\n *    - \"query string\" is the search term\n *    - maxAllowedDistance is the maximum Levenshtein distance allowed (default is 2)\n *\n * 4. The search returns an array of items sorted by their Levenshtein distance\n *    from the query string, with the closest matches first.\n *\n * Example:\n * const trie = new FuzzyTrie<string>();\n * trie.addItemToTrie(\"apple\", \"Apple fruit\");\n * trie.addItemToTrie(\"banana\", \"Banana fruit\");\n * const results = trie.searchItems(\"aple\", 1);\n * // results will contain [\"Apple fruit\"]\n */\n\ninterface TrieNode<T> {\n  // Map of child nodes, where keys are characters and values are their corresponding TrieNode objects\n  childNodes: Map<string, TrieNode<T>>;\n  // Flag indicating if this node represents a complete word in the trie\n  isCompleteWord: boolean;\n  // Array of items associated with this node and their corresponding search strings\n  associatedItems: Array<{ storedItem: T; associatedSearchString: string }>;\n}\n\n/*\n * @class FuzzyTrie<T>\n * @template T The type of items stored in the trie\n */\nclass FuzzyTrie<T> {\n  // The root node of the Trie\n  private rootNode: TrieNode<T> = {\n    childNodes: new Map(),\n    isCompleteWord: false,\n    associatedItems: [],\n  };\n\n  // Calculates the Levenshtein distance between two strings, up to a maximum allowed distance\n  private calculateLevenshteinDistance(\n    firstString: string,\n    secondString: string,\n    maxAllowedDistance: number = Infinity\n  ): number {\n    if (firstString === secondString) return 0;\n    if (firstString.length > secondString.length)\n      [firstString, secondString] = [secondString, firstString];\n\n    const firstStringLength = firstString.length;\n    const secondStringLength = secondString.length;\n\n    // If the difference in length exceeds maxAllowedDistance, immediately return a large value\n    if (secondStringLength - firstStringLength > maxAllowedDistance)\n      return maxAllowedDistance + 1;\n\n    let previousDistanceRow = new Array(firstStringLength + 1);\n    for (let i = 0; i <= firstStringLength; i++) {\n      previousDistanceRow[i] = i;\n    }\n\n    // Iterate over each character of the second string\n    for (\n      let currentIndex = 1;\n      currentIndex <= secondStringLength;\n      currentIndex++\n    ) {\n      const currentDistanceRow = [currentIndex];\n      const secondStringChar = secondString.charCodeAt(currentIndex - 1);\n      let minimumDistanceInRow = currentIndex;\n\n      // Iterate over each character of the first string\n      for (\n        let compareIndex = 1;\n        compareIndex <= firstStringLength;\n        compareIndex++\n      ) {\n        const firstStringChar = firstString.charCodeAt(compareIndex - 1);\n        const substitutionCost = firstStringChar === secondStringChar ? 0 : 1;\n        const insertionCost = currentDistanceRow[compareIndex - 1] + 1;\n        const deletionCost = previousDistanceRow[compareIndex] + 1;\n        const substitutionOrMatchCost =\n          previousDistanceRow[compareIndex - 1] + substitutionCost;\n        // Calculate the minimum cost of substitution, insertion, or deletion\n        currentDistanceRow[compareIndex] = Math.min(\n          insertionCost,\n          deletionCost,\n          substitutionOrMatchCost\n        );\n        // Update the minimum distance in the row\n        minimumDistanceInRow = Math.min(\n          minimumDistanceInRow,\n          currentDistanceRow[compareIndex]\n        );\n      }\n\n      // If the minimum distance exceeds maxAllowedDistance, return a large value\n      if (minimumDistanceInRow > maxAllowedDistance) {\n        return maxAllowedDistance + 1;\n      }\n\n      // Update the previous distance row for the next iteration\n      previousDistanceRow = currentDistanceRow;\n    }\n\n    // Return the final Levenshtein distance between the two strings\n    return previousDistanceRow[firstStringLength];\n  }\n\n  // Adds an item to the Trie based on a search string\n  addItemToTrie(searchString: string, itemToAdd: T) {\n    let currentNode = this.rootNode;\n    const normalizedSearchString = searchString.toLowerCase();\n\n    // Traverse the Trie to find or create nodes for each character in the search string\n    for (const currentChar of normalizedSearchString) {\n      if (!currentNode.childNodes.has(currentChar)) {\n        currentNode.childNodes.set(currentChar, {\n          childNodes: new Map(),\n          isCompleteWord: false,\n          associatedItems: [],\n        });\n      }\n      currentNode = currentNode.childNodes.get(currentChar)!;\n    }\n\n    // Mark the current node as a complete word and associate it with the item\n    currentNode.isCompleteWord = true;\n    currentNode.associatedItems.push({\n      storedItem: itemToAdd,\n      associatedSearchString: normalizedSearchString,\n    });\n  }\n\n  // Searches for items based on a query string, considering a maximum allowed distance\n  searchItems(queryString: string, maxAllowedDistance: number = 2): T[] {\n    const normalizedQuery = queryString.toLowerCase();\n    const searchResults: Array<{\n      matchedItem: T;\n      levenshteinDistance: number;\n      matchedSearchString: string;\n    }> = [];\n\n    // Helper function to traverse the Trie nodes\n    const traverseTrieNodes = (\n      currentNode: TrieNode<T>,\n      currentPrefix: string\n    ) => {\n      // If the current node represents a complete word, check its associated items\n      if (currentNode.isCompleteWord) {\n        currentNode.associatedItems.forEach(\n          ({ storedItem, associatedSearchString }) => {\n            // Calculate the Levenshtein distance between the query and the associated search string\n            const calculatedDistance = this.calculateLevenshteinDistance(\n              normalizedQuery,\n              associatedSearchString,\n              maxAllowedDistance\n            );\n\n            // If the distance is within the allowed limit, add the item to the results\n            if (calculatedDistance <= maxAllowedDistance) {\n              searchResults.push({\n                matchedItem: storedItem,\n                levenshteinDistance: calculatedDistance,\n                matchedSearchString: associatedSearchString,\n              });\n            }\n          }\n        );\n      }\n\n      // Recursively traverse each child node of the current node\n      currentNode.childNodes.forEach((childNode, charToChild) => {\n        traverseTrieNodes(childNode, currentPrefix + charToChild);\n      });\n    };\n\n    // Start the traversal from the root node with an empty prefix\n    traverseTrieNodes(this.rootNode, \"\");\n\n    // Sort the matched items by their Levenshtein distance\n    const sortedMatchedItems = searchResults\n      .sort((a, b) => a.levenshteinDistance - b.levenshteinDistance)\n      .map((result) => result.matchedItem);\n\n    // Return the sorted list of matched items\n    return sortedMatchedItems;\n  }\n}\n\nexport { FuzzyTrie };\n","import { useRef, useState, useEffect, useMemo } from \"react\";\nimport { FuzzyTrie } from \"./FuzzyTrieClass\";\n\n/**\n * A React hook that implements a fuzzy search trie for efficient searching and suggestions.\n *\n * @template T The type of items stored in the trie.\n * @param {UseTrieOptions<T>} options - Configuration options for the fuzzy trie.\n * @param {T[]} [options.items=[]] - An array of items to be added to the trie.\n * @param {(item: T) => string} options.getSearchString - A function that returns the search string for each item.\n * @param {number} [options.debounceMs=150] - The debounce time in milliseconds for the search function.\n * @param {number} [options.minSearchLength=1] - The minimum length of the search term to start searching.\n * @param {number} [options.maxDistance=2] - The maximum Levenshtein distance for fuzzy matching.\n * @param {number} [options.maxResults=10] - The maximum number of results to return.\n * @returns {{\n *   search: (searchTerm: string) => void,\n *   suggestions: T[]\n * }} An object containing the search function and current suggestions.\n */\nfunction useFuzzyFilter<T>({\n  items = [],\n  getSearchString,\n  debounceMs = 150,\n  minSearchLength = 1,\n  maxDistance = 2,\n  maxResults = 10,\n}: UseTrieOptions<T>) {\n  const trieRef = useRef(new FuzzyTrie<T>());\n  const [suggestions, setSuggestions] = useState<T[]>([]);\n\n  useEffect(() => {\n    trieRef.current = new FuzzyTrie<T>();\n    items.forEach((item) => {\n      trieRef.current.addItemToTrie(getSearchString(item), item);\n    });\n  }, [items, getSearchString]);\n\n  const search = useMemo(\n    () =>\n      debounce((searchTerm: string) => {\n        if (searchTerm.length < minSearchLength) {\n          setSuggestions([]);\n          return;\n        }\n        const results = trieRef.current\n          .searchItems(searchTerm, maxDistance)\n          .slice(0, maxResults);\n\n        setSuggestions(results);\n      }, debounceMs),\n    [minSearchLength, maxDistance, maxResults, debounceMs]\n  );\n\n  return { search, suggestions };\n}\n\nfunction debounce<T extends unknown[]>(\n  func: (...args: T) => void,\n  delay: number\n): (...args: T) => void {\n  let timer: NodeJS.Timeout | undefined;\n  return (...args: T) => {\n    if (timer) {\n      clearTimeout(timer);\n    }\n    timer = setTimeout(() => {\n      func(...args);\n    }, delay);\n  };\n}\n\nexport { useFuzzyFilter };\n\ninterface UseTrieOptions<T> {\n  items?: T[];\n  getSearchString: (item: T) => string;\n  debounceMs?: number;\n  minSearchLength?: number;\n  maxDistance?: number;\n  maxResults?: number;\n}\n"],"names":[],"mappings":"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"}