diff --git a/package.json b/package.json index e970d58..efd2def 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ztimson/ai-utils", - "version": "1.2.12", + "version": "1.2.13", "description": "AI Utility library", "author": "Zak Timson", "license": "MIT", diff --git a/src/llm.ts b/src/llm.ts index 776352c..e377387 100644 --- a/src/llm.ts +++ b/src/llm.ts @@ -397,15 +397,37 @@ class LLM { * @param {string} searchTerms Multiple search terms to check against target * @returns {{avg: number, max: number, similarities: number[]}} Similarity values 0-1: 0 = unique, 1 = identical */ - fuzzyMatch(target: string, ...searchTerms: string[]) { - if(searchTerms.length < 2) throw new Error('Requires at least 2 strings to compare'); - const vector = (text: string, dimensions: number = 10): number[] => { - return text.toLowerCase().split('').map((char, index) => - (char.charCodeAt(0) * (index + 1)) % dimensions / dimensions).slice(0, dimensions); - } - const v = vector(target); - const similarities = searchTerms.map(t => vector(t)).map(refVector => this.cosineSimilarity(v, refVector)); - return {avg: similarities.reduce((acc, s) => acc + s, 0) / similarities.length, max: Math.max(...similarities), similarities}; + fuzzyMatch(target, ...searchTerms) { + if (searchTerms.length < 2) throw new Error('Requires at least 2 strings to compare'); + + const levenshtein = (a, b) => { + const m = a.length, n = b.length; + if (!m) return n; + if (!n) return m; + const dp = Array.from({length: m + 1}, (_, i) => [i, ...Array(n).fill(0)]); + for (let j = 0; j <= n; j++) dp[0][j] = j; + for (let i = 1; i <= m; i++) { + for (let j = 1; j <= n; j++) { + dp[i][j] = a[i - 1] === b[j - 1] + ? dp[i - 1][j - 1] + : 1 + Math.min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]); + } + } + return dp[m][n]; + }; + + const similarity = (a, b) => { + a = a.toLowerCase(); b = b.toLowerCase(); + const dist = levenshtein(a, b); + return 1 - dist / Math.max(a.length, b.length, 1); + }; + + const similarities = searchTerms.map(t => similarity(target, t)); + return { + avg: similarities.reduce((acc, s) => acc + s, 0) / similarities.length, + max: Math.max(...similarities), + similarities + }; } /**