/* eslint-disable */ /** * Fuzzy search ripped from the internet. */ const FuzzySet: ( arr?: string[], useLevenshtein?: boolean, gramSizeLower?: number, gramSizeUpper?: number, ) => any = function ( arr: string[] = [], useLevenshtein?: boolean, gramSizeLower: number = 2, gramSizeUpper: number = 3, ) { var fuzzyset: any = { gramSizeLower: gramSizeLower, gramSizeUpper: gramSizeUpper, useLevenshtein: typeof useLevenshtein !== 'boolean' ? true : useLevenshtein, exactSet: {}, matchDict: {}, items: {}, }; var levenshtein = function (str1: string, str2: string) { var current: number[] = []; var prev: number; var value: number; for (var i = 0; i <= str2.length; i++) { for (var j = 0; j <= str1.length; j++) { if (i && j) { if (str1.charAt(j - 1) === str2.charAt(i - 1)) { // @ts-expect-error value = prev; } else { // @ts-expect-error value = Math.min(current[j], current[j - 1], prev) + 1; } } else { value = i + j; } prev = current[j]; current[j] = value; } } return current.pop() as number; }; // return an edit distance from 0 to 1 var _distance = function (str1: string, str2: string) { if (str1 === null && str2 === null) { throw new Error('Trying to compare two null values'); } if (str1 === null || str2 === null) { return 0; } str1 = String(str1); str2 = String(str2); var distance = levenshtein(str1, str2); if (str1.length > str2.length) { return 1 - distance / str1.length; } else { return 1 - distance / str2.length; } }; var _nonWordRe = /[^a-zA-Z0-9\u00C0-\u00FF, ]+/g; var _iterateGrams = function (value: any, gramSize: any) { gramSize = gramSize || 2; var simplified = '-' + value.toLowerCase().replace(_nonWordRe, '') + '-', lenDiff = gramSize - simplified.length, results = []; if (lenDiff > 0) { for (var i = 0; i < lenDiff; ++i) { simplified += '-'; } } for (var i = 0; i < simplified.length - gramSize + 1; ++i) { results.push(simplified.slice(i, i + gramSize)); } return results; }; var _gramCounter = function (value: string, gramSize: number) { // return an object where key=gram, value=number of occurrences gramSize = gramSize || 2; var result = {}, grams = _iterateGrams(value, gramSize), i = 0; for (i; i < grams.length; ++i) { if (grams[i] in result) { // @ts-expect-error result[grams[i]] += 1; } else { // @ts-expect-error result[grams[i]] = 1; } } return result; }; // the main functions fuzzyset.get = function (value: string, defaultValue: string, minMatchScore: number) { // check for value in set, returning defaultValue or null if none found if (minMatchScore === undefined) { minMatchScore = 0.33; } var result = this._get(value, minMatchScore); if (!result && typeof defaultValue !== 'undefined') { return defaultValue; } return result; }; fuzzyset._get = function (value: string, minMatchScore: number) { var results = []; // start with high gram size and if there are no results, go to lower gram sizes for (var gramSize = this.gramSizeUpper; gramSize >= this.gramSizeLower; --gramSize) { results = this.__get(value, gramSize, minMatchScore); if (results && results.length > 0) { return results; } } return null; }; fuzzyset.__get = function (value: string, gramSize: number, minMatchScore: number) { var normalizedValue = this._normalizeStr(value), matches = {}, gramCounts = _gramCounter(normalizedValue, gramSize), items = this.items[gramSize], sumOfSquareGramCounts = 0, gram, gramCount, i, index, otherGramCount; for (gram in gramCounts) { // @ts-expect-error gramCount = gramCounts[gram]; sumOfSquareGramCounts += Math.pow(gramCount, 2); if (gram in this.matchDict) { for (i = 0; i < this.matchDict[gram].length; ++i) { index = this.matchDict[gram][i][0]; otherGramCount = this.matchDict[gram][i][1]; if (index in matches) { // @ts-expect-error matches[index] += gramCount * otherGramCount; } else { // @ts-expect-error matches[index] = gramCount * otherGramCount; } } } } function isEmptyObject(obj: any) { for (var prop in obj) { if (obj.hasOwnProperty(prop)) { return false; } } return true; } if (isEmptyObject(matches)) { return null; } var vectorNormal = Math.sqrt(sumOfSquareGramCounts), results: string[] = [], matchScore; // build a results list of [score, str] for (var matchIndex in matches) { // @ts-expect-error matchScore = matches[matchIndex]; // @ts-expect-error results.push([matchScore / (vectorNormal * items[matchIndex][0]), items[matchIndex][1]]); } var sortDescending = function (a: any, b: any) { if (a[0] < b[0]) { return 1; } else if (a[0] > b[0]) { return -1; } else { return 0; } }; results.sort(sortDescending); if (this.useLevenshtein) { var newResults: string[] = [], endIndex = Math.min(50, results.length); // truncate somewhat arbitrarily to 50 // @ts-expect-error for (var i = 0; i < endIndex; ++i) { // @ts-expect-error newResults.push([_distance(results[i][1], normalizedValue), results[i][1]]); } results = newResults; results.sort(sortDescending); } newResults = []; results.forEach( function (scoreWordPair: any) { if (scoreWordPair[0] >= minMatchScore) { // @ts-expect-error newResults.push([scoreWordPair[0], this.exactSet[scoreWordPair[1]]]); } }.bind(this), ); return newResults; }; fuzzyset.add = function (value: any) { var normalizedValue = this._normalizeStr(value); if (normalizedValue in this.exactSet) { return false; } var i = this.gramSizeLower; for (i; i < this.gramSizeUpper + 1; ++i) { this._add(value, i); } }; fuzzyset._add = function (value: string, gramSize: number) { var normalizedValue = this._normalizeStr(value), items = this.items[gramSize] || [], index = items.length; items.push(0); var gramCounts = _gramCounter(normalizedValue, gramSize); var sumOfSquareGramCounts = 0; var gram: string; var gramCount: number; for (gram in gramCounts) { // @ts-expect-error gramCount = gramCounts[gram]; sumOfSquareGramCounts += Math.pow(gramCount, 2); if (gram in this.matchDict) { this.matchDict[gram].push([index, gramCount]); } else { this.matchDict[gram] = [[index, gramCount]]; } } var vectorNormal = Math.sqrt(sumOfSquareGramCounts); items[index] = [vectorNormal, normalizedValue]; this.items[gramSize] = items; this.exactSet[normalizedValue] = value; }; fuzzyset._normalizeStr = function (str: string) { if (Object.prototype.toString.call(str) !== '[object String]') { throw new Error('Must use a string as argument to FuzzySet functions'); } return str.toLowerCase(); }; // return length of items in set fuzzyset.length = function () { var count = 0, prop; for (prop in this.exactSet) { if (this.exactSet.hasOwnProperty(prop)) { count += 1; } } return count; }; // return is set is empty fuzzyset.isEmpty = function () { for (var prop in this.exactSet) { if (this.exactSet.hasOwnProperty(prop)) { return false; } } return true; }; // return list of values loaded into set fuzzyset.values = function () { var values = [], prop; for (prop in this.exactSet) { if (this.exactSet.hasOwnProperty(prop)) { values.push(this.exactSet[prop]); } } return values; }; // initialization var i = fuzzyset.gramSizeLower; for (i; i < fuzzyset.gramSizeUpper + 1; ++i) { fuzzyset.items[i] = []; } // add all the items to the set for (i = 0; i < arr.length; ++i) { fuzzyset.add(arr[i]); } return fuzzyset; }; export default FuzzySet;