UNPKG

fuzzy-rater

Version:

Tooling for fuzzily rating text according to some query

234 lines 18.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FuzzyRater = void 0; const WordOrderMatcher_1 = require("./orderMatcher/WordOrderMatcher"); const FuzzyMultiWordMatcher_1 = require("./wordMatcher/FuzzyMultiWordMatcher"); const addAlterationData_1 = require("./addAlterationData"); /** * The highest level fuzzy rater class, to rate how well text matches an input query */ class FuzzyRater { /** * Creates a new fuzzy rater * @param query The query to be rate with * @param config The configuration for how to rate matches */ constructor(query, config) { const normalized = this.initializeInputs(query, config); this.config = normalized.config; this.query = normalized.query; this.wordMatchers = this.initializeWordMatchers(); this.orderMatcher = this.initializeOrderMatcher(); } /** * Initializes the rater's configuration * @param query The query input * @param config The config input * @returns The normalized query and config with defaults */ initializeInputs(query, config) { const normalizedConfig = { fuzziness: { 2: 1, 5: 2 }, skipPenalty: 10, missingPenalty: 10, typoPenalty: 10, extraBonus: 5, ...config, }; if (typeof query == "string") query = query.split(" ").map(word => ({ word })); const fuzziness = Object.entries(normalizedConfig.fuzziness) .map(([a, b]) => [Number(a), b]) .sort(([a], [b]) => a - b); const normalizedQuery = query.map(input => ({ extraBonus: normalizedConfig.extraBonus, maxDistance: fuzziness.reduce((cur, item) => (input.word.length > item[0] ? item[1] : cur), 0), skipPenalty: normalizedConfig.skipPenalty, ...input, })); return { query: normalizedQuery, config: normalizedConfig }; } /** * Initializes the word matches * @returns The word matchers */ initializeWordMatchers() { return this.query.reduce((matchers, { word, maxDistance }, i) => { const alreadyContains = matchers.find(({ matcher }) => matcher.word == word); if (alreadyContains) matchers.map(({ count, matcher }) => matcher.word == word ? { count: count + 1, matcher } : { count, matcher }); return [ ...matchers, { count: 1, matcher: new FuzzyMultiWordMatcher_1.FuzzyMultiWordMatcher(word, maxDistance) }, ]; }, []); } /** * Initializes the word order matcher * @returns The word matcher */ initializeOrderMatcher() { return new WordOrderMatcher_1.WordOrderMatcher(this.query.map(q => ({ word: q.word, penalty: q.skipPenalty }))); } /** * Merges two match lists * @param m1 The first (sorted) match list * @param m2 The second (sorted) match list * @returns The combined, sorted, match list */ mergeMatches(m1, m2) { const combined = []; let i = 0, j = 0; while (i < m1.length && j < m2.length) { if (m1[i].index < m2[j].index) combined.push(m1[i++]); else combined.push(m2[j++]); } while (i < m1.length) combined.push(m1[i++]); while (j < m2.length) combined.push(m2[j++]); return combined; } /** * Computes all relevant match data for the given text * @param text The text to get match data for * @returns */ getMatch(text) { const { missingCost, extraBonus, matches } = this.wordMatchers.reduce(({ missingCost, extraBonus, matches: prevMatches }, { count: requireWordCount, matcher }) => { // Retrieve the matches for this matcher const matches = matcher.getMatch(text); const word = matcher.word; const normalized = matches.map(match => ({ ...match, word, index: match.endIndex - word.length + match.distance, cost: match.distance * this.config.typoPenalty, })); return { missingCost: missingCost + (matches.length == 0 ? this.config.missingPenalty : 0), // Note: It doesn't consider distance or typos in extra matches extraBonus: extraBonus + Math.max(0, matches.length - requireWordCount) * this.config.extraBonus, matches: this.mergeMatches(prevMatches, normalized), }; }, { missingCost: 0, extraBonus: 0, matches: [], }); if (matches.length > 0) { const orderCost = this.orderMatcher.getMatch(matches); const combinedScore = orderCost + missingCost - extraBonus; return { score: combinedScore, orderCost, missingCost, extraBonus, }; } return { score: Infinity, orderCost: Infinity, missingCost: Infinity, extraBonus: 0, }; } /** * Retrieves the score for a given piece of text * @param text The text to be rated * @returns A score, where large is worse */ getScore(text) { return this.getMatch(text).score; } /** * Retrieves the match data that can be used for highlighting * @param text The text to be matched * @returns The scores including match data */ getMatchData(text) { const { missingCost, extraBonus, matches, alterations } = this.wordMatchers.reduce(({ missingCost, extraBonus, matches: prevMatches, alterations }, { count: requireWordCount, matcher }) => { // Retrieve the matches for this matcher const matchData = matcher.getMatchData(text); const word = matcher.word; const matches = matchData.alterations.reduce((matches, alteration) => { if (alteration.query.index == 0) { const distance = matchData.distances[matches.length]; return [ ...matches, { word, index: alteration.target.index, endIndex: alteration.target.index + word.length - distance, cost: distance * this.config.typoPenalty, }, ]; } return matches; }, []); return { missingCost: missingCost + (matchData.distances.length == 0 ? this.config.missingPenalty : 0), // Note: It doesn't consider distance or typos in extra matches extraBonus: extraBonus + Math.max(0, matchData.distances.length - requireWordCount) * this.config.extraBonus, matches: this.mergeMatches(prevMatches, matches), alterations: [ ...alterations, { word, alterations: matchData.alterations }, ], }; }, { missingCost: 0, extraBonus: 0, alterations: [], matches: [], }); if (matches.length > 0) { const orderMatchData = this.orderMatcher.getMatchData(matches); const orderCost = orderMatchData.distance; // Combine all alterations data const words = this.query.map(({ word: text }, index) => { var _a; const match = orderMatchData.matches.find(m => m.wordIndex == index); return { text, index, matchedIndex: (_a = match === null || match === void 0 ? void 0 : match.match.index) !== null && _a !== void 0 ? _a : -1, }; }); const matchGroups = words.reduce((groups, word) => { const wordAlterations = alterations.find(({ word: w }) => w == word.text); if (wordAlterations) return addAlterationData_1.addAlterationData(groups, wordAlterations.alterations, word); return groups; }, []); // Return the final result const combinedScore = orderCost + missingCost - extraBonus; return { matchGroups, score: combinedScore, orderCost, missingCost, extraBonus, }; } return { matchGroups: [], score: Infinity, orderCost: Infinity, missingCost: Infinity, extraBonus: 0, }; } } exports.FuzzyRater = FuzzyRater; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiRnV6enlSYXRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9mdXp6eVJhdGUvRnV6enlSYXRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxzRUFBaUU7QUFFakUsK0VBQTBFO0FBTTFFLDJEQUFzRDtBQUd0RDs7R0FFRztBQUNILE1BQWEsVUFBVTtJQU9uQjs7OztPQUlHO0lBQ0gsWUFBbUIsS0FBaUMsRUFBRSxNQUF5QjtRQUMzRSxNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsS0FBSyxFQUFFLE1BQU0sQ0FBQyxDQUFDO1FBQ3hELElBQUksQ0FBQyxNQUFNLEdBQUcsVUFBVSxDQUFDLE1BQU0sQ0FBQztRQUNoQyxJQUFJLENBQUMsS0FBSyxHQUFHLFVBQVUsQ0FBQyxLQUFLLENBQUM7UUFDOUIsSUFBSSxDQUFDLFlBQVksR0FBRyxJQUFJLENBQUMsc0JBQXNCLEVBQUUsQ0FBQztRQUNsRCxJQUFJLENBQUMsWUFBWSxHQUFHLElBQUksQ0FBQyxzQkFBc0IsRUFBRSxDQUFDO0lBQ3RELENBQUM7SUFFRDs7Ozs7T0FLRztJQUNPLGdCQUFnQixDQUN0QixLQUFpQyxFQUNqQyxNQUF5QjtRQUV6QixNQUFNLGdCQUFnQixHQUFHO1lBQ3JCLFNBQVMsRUFBRSxFQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBQztZQUN2QixXQUFXLEVBQUUsRUFBRTtZQUNmLGNBQWMsRUFBRSxFQUFFO1lBQ2xCLFdBQVcsRUFBRSxFQUFFO1lBQ2YsVUFBVSxFQUFFLENBQUM7WUFDYixHQUFHLE1BQU07U0FDWixDQUFDO1FBRUYsSUFBSSxPQUFPLEtBQUssSUFBSSxRQUFRO1lBQUUsS0FBSyxHQUFHLEtBQUssQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQyxFQUFDLElBQUksRUFBQyxDQUFDLENBQUMsQ0FBQztR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