{"ScriptPreparationCode":"var books = [];\r\nvar xmlhttp = new XMLHttpRequest();\r\nxmlhttp.onreadystatechange = function() {\r\n if (xmlhttp.readyState == 4 \u0026\u0026 xmlhttp.status == 200) {\r\n var json = JSON.parse(xmlhttp.responseText);\r\n books = json.books;\r\n }\r\n}\r\nxmlhttp.open(\u0027GET\u0027, \u0027https://bvaughn.github.io/js-search/books.json\u0027, false);\r\nxmlhttp.send();\r\n\r\nvar search = new JsSearch.Search(\u0027isbn\u0027);\r\nsearch.addIndex(\u0027title\u0027);\r\nsearch.addDocuments(books);\r\n\r\nvar titles = books.map(b =\u003E b.title);\r\nvar searchTerms = [\u0027letter\u0027, \u0027world\u0027, \u0027wife\u0027, \u0027love\u0027, \u0027foobar\u0027];\r\n// var searchTerms = [\u0027letter\u0027];\r\n\r\nvar fuzzy = {};\r\n\r\nvar __assign = (this \u0026\u0026 this.__assign) || function () {\r\n __assign = Object.assign || function(t) {\r\n for (var s, i = 1, n = arguments.length; i \u003C n; i\u002B\u002B) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))\r\n t[p] = s[p];\r\n }\r\n return t;\r\n };\r\n return __assign.apply(this, arguments);\r\n};\r\n(function (factory) {\r\n factory(_, fuzzy)\r\n})(function (lodash, exports) {\r\n \u0022use strict\u0022;\r\n Object.defineProperty(exports, \u0022__esModule\u0022, { value: true });\r\n exports.filterAndSort = exports.splitByMatches = void 0;\r\n var lodash_1 = lodash;\r\n var DEBUG = false;\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n var matchCache = new Map();\r\n var wordsCache = new Map();\r\n var wordsWithoutSeparatorsCache = new Map();\r\n var stringCache = new Map();\r\n var getJaccardCoefficient = function (_a) {\r\n var matchText = _a.matchText, searchWord = _a.searchWord, targetWord = _a.targetWord;\r\n if (matchText.length === 0) {\r\n return 0;\r\n }\r\n var intersection = matchText.length;\r\n var union = targetWord.length \u002B searchWord.length - intersection;\r\n return intersection / union;\r\n };\r\n var normalize = function (_a) {\r\n var string = _a.string;\r\n if (stringCache.has(string)) {\r\n return stringCache.get(string);\r\n }\r\n var stringNormalized = string\r\n // https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript/37511463#37511463\r\n .normalize(\u0027NFD\u0027)\r\n .replace(/[\\u0300-\\u036f]/g, \u0027\u0027)\r\n .replace(/[\u0027\u0022\u201C\u201D\u2019\u2018.,]/g, \u0027\u0027)\r\n .replace(/-/g, \u0027 \u0027)\r\n .toLowerCase();\r\n stringCache.set(string, stringNormalized);\r\n return stringNormalized;\r\n };\r\n var SEPARATOR_CHARACTERS = [\u0027,\u0027, \u0027.\u0027, \u0027-\u0027, \u0027 \u0027, \u0027|\u0027, \u0027/\u0027, \u0027(\u0027, \u0027)\u0027, \u0027[\u0027, \u0027]\u0027];\r\n var REGEXP_SEPARATORS = new RegExp(\u0022[\\\\\u0022 \u002B SEPARATOR_CHARACTERS.join(\u0027\\\\\u0027) \u002B \u0022]\u002B\u0022);\r\n var REGEXP_SEPARATORS_CAPTURING = new RegExp(\u0022([\\\\\u0022 \u002B SEPARATOR_CHARACTERS.join(\u0027\\\\\u0027) \u002B \u0022])\u0022);\r\n var splitWords = function (_a) {\r\n var text = _a.text, isCapturingSeparators = _a.isCapturingSeparators;\r\n var cache = isCapturingSeparators ? wordsWithoutSeparatorsCache : wordsCache;\r\n if (cache.has(text)) {\r\n return cache.get(text);\r\n }\r\n var regExp = isCapturingSeparators\r\n ? REGEXP_SEPARATORS_CAPTURING\r\n : REGEXP_SEPARATORS;\r\n var words = text.split(regExp).filter(function (value) { return value; });\r\n cache.set(text, words);\r\n return words;\r\n };\r\n var hasAnySeparator = function (_a) {\r\n var word = _a.word;\r\n return SEPARATOR_CHARACTERS.some(function (character) { return word.includes(character); });\r\n };\r\n var pushMatchCharacter = function (_a) {\r\n var matchCharacters = _a.matchCharacters, match = _a.match, value = _a.value, valueNormalized = _a.valueNormalized;\r\n var previousMatchCharacter = matchCharacters[matchCharacters.length - 1];\r\n if (previousMatchCharacter != null \u0026\u0026\r\n previousMatchCharacter.match === match) {\r\n previousMatchCharacter.value \u002B= value;\r\n previousMatchCharacter.valueNormalized \u002B= valueNormalized;\r\n }\r\n else {\r\n matchCharacters.push({\r\n value: value,\r\n valueNormalized: valueNormalized,\r\n match: match,\r\n });\r\n }\r\n };\r\n var getWordMatchCharacters = function (_a) {\r\n var targetWord = _a.targetWord, searchWordNormalized = _a.searchWordNormalized, targetWordNormalized = _a.targetWordNormalized;\r\n if (SEPARATOR_CHARACTERS.includes(targetWordNormalized)) {\r\n return [];\r\n }\r\n if (hasAnySeparator({ word: targetWordNormalized }) ||\r\n hasAnySeparator({ word: searchWordNormalized })) {\r\n throw new Error(\u0027Neither the targetWordNormalized nor the searchWordNormalized can contain separator characters\u0027);\r\n }\r\n var matchCharacters = [];\r\n var searchIndex = 0;\r\n var targetIndex = 0;\r\n var lastMatchedTargetIndex = -1;\r\n while (true) {\r\n var searchCharacter = searchWordNormalized[searchIndex];\r\n var targetCharacter = targetWordNormalized[targetIndex];\r\n if (searchCharacter == null \u0026\u0026 targetCharacter == null) {\r\n return matchCharacters;\r\n }\r\n if (searchCharacter == null) {\r\n pushMatchCharacter({\r\n matchCharacters: matchCharacters,\r\n match: false,\r\n value: targetWord.slice(targetIndex),\r\n valueNormalized: targetWordNormalized.slice(targetIndex),\r\n });\r\n return matchCharacters;\r\n }\r\n if (targetCharacter == null \u0026\u0026 searchWordNormalized[searchIndex] != null) {\r\n searchIndex \u002B= 1;\r\n targetIndex = lastMatchedTargetIndex \u002B 1;\r\n matchCharacters.pop();\r\n continue;\r\n }\r\n if (targetCharacter == null) {\r\n return matchCharacters;\r\n }\r\n var match = searchCharacter === targetCharacter;\r\n pushMatchCharacter({\r\n matchCharacters: matchCharacters,\r\n match: match,\r\n value: targetWord[targetIndex],\r\n valueNormalized: targetWordNormalized[targetIndex],\r\n });\r\n if (match) {\r\n lastMatchedTargetIndex = targetIndex;\r\n searchIndex \u002B= 1;\r\n }\r\n targetIndex \u002B= 1;\r\n }\r\n return matchCharacters;\r\n };\r\n var getWordMatch = function (_a) {\r\n var pairs = _a.pairs, index = _a.index, searchWord = _a.searchWord, targetWord = _a.targetWord;\r\n var searchWordNormalized = normalize({ string: searchWord });\r\n var targetWordNormalized = normalize({ string: targetWord });\r\n var matchCharacters = typeof pairs[index] === \u0027string\u0027\r\n ? getWordMatchCharacters({\r\n targetWord: targetWord,\r\n searchWordNormalized: searchWordNormalized,\r\n targetWordNormalized: targetWordNormalized,\r\n })\r\n : [];\r\n var matchText = matchCharacters.reduce(function (accumulator, matchCharacter) {\r\n return accumulator \u002B (matchCharacter.match ? matchCharacter.value : \u0027\u0027);\r\n }, \u0027\u0027);\r\n return {\r\n index: index,\r\n coefficient: getJaccardCoefficient({ matchText: matchText, searchWord: searchWord, targetWord: targetWord }),\r\n isExact: targetWordNormalized.startsWith(searchWordNormalized),\r\n matchCharacters: matchCharacters,\r\n matchText: matchText,\r\n searchWord: searchWord,\r\n targetWord: targetWord,\r\n searchWordNormalized: searchWordNormalized,\r\n targetWordNormalized: targetWordNormalized,\r\n };\r\n };\r\n var getBestWordMatch = function (_a) {\r\n var pairs = _a.pairs, searchWord = _a.searchWord, targetWords = _a.targetWords;\r\n return lodash_1.chain(targetWords)\r\n .map(function (targetWord, index) {\r\n return getWordMatch({ pairs: pairs, index: index, searchWord: searchWord, targetWord: targetWord });\r\n })\r\n .filter(function (match) { return match.coefficient; })\r\n .sortBy(function (match) { return match.coefficient * -1; })\r\n .sortBy(function (match) { return (match.isExact ? -1 : 1); })\r\n .first()\r\n .value();\r\n };\r\n var getMatch = function (_a) {\r\n var item = _a.item, searchText = _a.searchText, targetText = _a.targetText;\r\n var cacheKey = searchText \u002B \u0022:\u0022 \u002B targetText;\r\n if (matchCache.has(cacheKey)) {\r\n var cachedMatch = matchCache.get(cacheKey);\r\n return __assign(__assign({}, cachedMatch), { item: item });\r\n }\r\n var searchWords = splitWords({\r\n text: searchText,\r\n isCapturingSeparators: false,\r\n });\r\n var targetWords = splitWords({\r\n text: targetText,\r\n isCapturingSeparators: true,\r\n });\r\n var targetWordsWithoutSeparators = splitWords({\r\n text: targetText,\r\n isCapturingSeparators: false,\r\n });\r\n var pairs = targetWords.slice(0);\r\n searchWords.forEach(function (searchWord) {\r\n var bestMatch = getBestWordMatch({ pairs: pairs, searchWord: searchWord, targetWords: targetWords });\r\n if (bestMatch != null) {\r\n pairs[bestMatch.index] = bestMatch;\r\n }\r\n });\r\n var match = {\r\n pairs: pairs,\r\n searchText: searchText,\r\n searchWords: searchWords,\r\n targetText: targetText,\r\n targetWords: targetWords,\r\n coefficient: getJaccardCoefficient({\r\n matchText: pairs\r\n .map(function (pair) { return (typeof pair === \u0027string\u0027 ? \u0027\u0027 : pair.matchText); })\r\n .join(\u0027\u0027),\r\n searchWord: searchWords.join(\u0027\u0027),\r\n targetWord: targetWordsWithoutSeparators.join(\u0027\u0027),\r\n }),\r\n isIncluded: targetWords.some(function (targetWord) {\r\n return searchWords.some(function (searchWord) {\r\n return normalize({ string: targetWord }).includes(normalize({ string: searchWord }));\r\n });\r\n }),\r\n isExact: pairs.some(function (pair) {\r\n return typeof pair !== \u0027string\u0027 \u0026\u0026\r\n pair.matchCharacters.length \u003E 0 \u0026\u0026\r\n pair.matchCharacters[0].match === true \u0026\u0026\r\n pair.matchCharacters[0].valueNormalized.startsWith(pair.targetWordNormalized);\r\n }),\r\n isFull: pairs.some(function (pair) {\r\n return typeof pair !== \u0027string\u0027 \u0026\u0026\r\n pair.matchCharacters.length \u003E 0 \u0026\u0026\r\n pair.matchCharacters[0].match === true \u0026\u0026\r\n pair.matchCharacters[0].valueNormalized === pair.targetWordNormalized;\r\n }),\r\n };\r\n matchCache.set(cacheKey, match);\r\n return Object.assign(match, { item: item });\r\n };\r\n var reduceConsecutiveStrings = function (values) {\r\n return values.reduce(function (accumulator, value) {\r\n if (typeof value === \u0027string\u0027 \u0026\u0026 typeof lodash_1.last(accumulator) === \u0027string\u0027) {\r\n accumulator[accumulator.length - 1] = lodash_1.last(accumulator) \u002B value;\r\n }\r\n else {\r\n accumulator.push(typeof value === \u0027string\u0027 ? value : value.matchCharacters);\r\n }\r\n return accumulator;\r\n }, []);\r\n };\r\n exports.splitByMatches = function (_a) {\r\n var searchText = _a.searchText, targetText = _a.targetText;\r\n if (searchText.length === 0) {\r\n return [targetText];\r\n }\r\n var match = getMatch({ item: targetText, searchText: searchText, targetText: targetText });\r\n return reduceConsecutiveStrings(match.pairs);\r\n };\r\n // TO:DO: Add cache\r\n exports.filterAndSort = function (_a) {\r\n var _b;\r\n var data = _a.data, _c = _a.getTargetText, getTargetText = _c === void 0 ? function (_a) {\r\n var item = _a.item;\r\n return item;\r\n } : _c, searchText = _a.searchText, _d = _a.sortIteratees, sortIteratees = _d === void 0 ? [function () { return 0; }] : _d;\r\n if (searchText.length === 0) {\r\n return data;\r\n }\r\n return ((_b = lodash_1.chain(data)\r\n // Get matches for each item\r\n .map(function (item) {\r\n var targetText = getTargetText({ item: item });\r\n if (typeof targetText !== \u0027string\u0027) {\r\n throw new Error(\u0027The target text to search through needs to be a string\u0027);\r\n }\r\n return getMatch({\r\n item: item,\r\n searchText: searchText,\r\n targetText: targetText,\r\n });\r\n })\r\n // Filter out non-matches\r\n .filter(function (match) {\r\n if (DEBUG) {\r\n // eslint-disable-next-line no-console\r\n console.debug(\u0027Match of %o in %o: isFull=%o isExact=%o isIncluded=%o coefficient=%o\u0027, match.searchText, match.targetText, match.isFull, match.isExact, match.isIncluded, match.coefficient);\r\n }\r\n return (match.isFull ||\r\n match.isExact ||\r\n match.isIncluded ||\r\n match.coefficient \u003E 0);\r\n })\r\n // Sort by the coefficient, then by included matches, and then exact matches\r\n .sortBy(function (match) { return match.coefficient * -1; })\r\n .sortBy(function (match) { return (match.isIncluded ? -1 : 1); })\r\n .sortBy(function (match) { return (match.isExact ? -1 : 1); })\r\n .sortBy(function (match) { return (match.isFull ? -1 : 1); })\r\n // Finally, sort by the number of exact matches in the pairs of matches\r\n .sortBy(function (match) {\r\n return match.pairs.reduce(function (accumulator, pair) {\r\n if (typeof pair !== \u0027string\u0027) {\r\n return accumulator - (pair.isExact ? 2 : 1);\r\n }\r\n return accumulator;\r\n }, 0);\r\n })).sortBy.apply(_b, sortIteratees).map(function (match) { return match.item; })\r\n .value());\r\n };\r\n exports.indexList = function (_a) {\r\n var data = _a.data, _b = _a.getTargetText, getTargetText = _b === void 0 ? function (_a) {\r\n var item = _a.item;\r\n return item;\r\n } : _b;\r\n data.forEach(function (item) {\r\n var targetText = getTargetText({ item: item });\r\n splitWords({\r\n text: targetText,\r\n isCapturingSeparators: true,\r\n });\r\n splitWords({\r\n text: targetText,\r\n isCapturingSeparators: false,\r\n });\r\n });\r\n };\r\n});\r\n\r\nfuzzy.indexList({\r\n data: titles,\r\n})","TestCases":[{"Name":"Custom fuzzy","Code":"for (var i = 0, length = searchTerms.length; i \u003C length; i\u002B\u002B) {\r\n const result1 = fuzzy.filterAndSort({\r\n data: titles,\r\n searchText: searchTerms[i],\r\n })\r\n // console.log({ result1 })\r\n}","IsDeferred":false},{"Name":"Fuzzysort","Code":"for (var i = 0, length = searchTerms.length; i \u003C length; i\u002B\u002B) {\r\n const result2 = fuzzysort.go(searchTerms[i], titles);\r\n // console.log({ result2 })\r\n}","IsDeferred":false},{"Name":"js-search","Code":"for (var i = 0, length = searchTerms.length; i \u003C length; i\u002B\u002B) {\r\n const result3 = search.search(searchTerms[i]);\r\n // console.log({ result3 })\r\n}","IsDeferred":false}]}