[READ-ONLY] Mirror of https://github.com/FoxxMD/string-sameness. Compare the sameness of two strings foxxmd.github.io/string-sameness/
compare cosine-similarity dice-coefficient levenshtein-distance sameness string text typescript
0

Configure Feed

Select the types of activity you want to include in your feed.

Implement strategies as a user-definable option

FoxxMD (Apr 4, 2023, 3:29 PM EDT) 5fa531a1 39150300

+389 -107
+103 -19
README.md
··· 1 1 # string-sameness 2 2 3 - Generate a score that represents how closely the same two strings are. The comparison function uses an average of: 4 - 5 - * [Dice's Coefficient](https://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient) 6 - * [Cosine Similarity](https://en.wikipedia.org/wiki/Cosine_similarity) 7 - * [Levenshtein Distance](https://en.wikipedia.org/wiki/Levenshtein_distance) 3 + Generate scores that represents how similar two strings are based on different string comparison algorithms. 8 4 9 - weighted by the length of the content being compared (more weight for longer content). 5 + Scores from all used algorithms are averaged and then weighted by the length of the content being compared (more weight for longer content). 10 6 11 7 The sameness is then given a **score of 0 to 100.** 12 8 13 9 * 0 => Totally unique pieces of content 14 10 * 100 => Identical content 15 11 16 - ## Install/Usage 12 + # Install/Usage 17 13 18 14 ``` 19 15 npm install @foxxmd/string-sameness ··· 24 20 25 21 const result = stringSameness('This is one sentence', 'This is another sentence'); 26 22 console.log(result); 27 - // { 28 - // "scores": { 29 - // "dice": 66.66, 30 - // "cosine": 75, 31 - // "leven": 79.16 32 - // }, 33 - // "highScore": 73.61, 34 - // "highScoreWeighted": 83.58 35 - // } 23 + //{ 24 + // "strategies": { 25 + // "dice": { 26 + // "score": 66.66666666666666 27 + // }, 28 + // "leven": { 29 + // "distance": 5, 30 + // "score": 79.16666666666666 31 + // }, 32 + // "cosine": { 33 + // "score": 75 34 + // } 35 + // }, 36 + // "highScore": 73.6111111111111, 37 + // "highScoreWeighted": 83.58977247888106 38 + //} 36 39 ``` 37 40 38 - ### Normalization 41 + # Options 42 + 43 + An optional third argument can be provided to `stringSameness` to customize how strings are normalized before comparison and what strategies are used for comparison. 44 + 45 + ## Strategies 46 + 47 + Pass a list of `ComparisonStrategy` objects using `{strategies: []}` to define which string comparisons should be performed on the given strings. 39 48 40 - A third argument may be passed to `stringSameness` to provide a list of functions to apply to the strings to compare. When not explicitly provided a default set of functions is applied to normalize the strings (to remove trivial differences): 49 + The average of the scores from all passed strategies is returned as `highScore` (and `highScoreWeighted`) from `stringSameness()` 50 + 51 + When no strategies are explicitly passed a default set of strategies is used, found in `@foxxmd/string-sameness/strategies`: 52 + 53 + * [Dice's Coefficient](https://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient) in [`diceSimilarities.ts`](/src/matchingStrategies/diceSimilarity.ts) 54 + * [Cosine Similarity](https://en.wikipedia.org/wiki/Cosine_similarity) in [`cosineSimilarities.ts`](/src/matchingStrategies/cosineSimilarity.ts) 55 + * [Levenshtein Distance](https://en.wikipedia.org/wiki/Levenshtein_distance) in [`levenSimilarities.ts`](/src/matchingStrategies/levenSimilarity.ts) 56 + 57 + ### Bring Your Own Strategy 58 + 59 + Use your own strategy by creating an object that conforms to `ComparisonStrategy`: 60 + 61 + ```ts 62 + export interface ComparisonStrategy { 63 + /** 64 + * The name of this strategy 65 + * */ 66 + name: string 67 + /** 68 + * A function that accepts two string arguments and returns a number between 0 and 100 signifying how closely similar the strings are: 69 + * 0 => not similar at all 70 + * 100 => identical 71 + * */ 72 + strategy: (strA: string, strB: string) => number 73 + /** 74 + * An optional function that accepts two string arguments and returns whether this strategy should be used 75 + * */ 76 + isValid?: (strA: string, strB: string) => boolean 77 + } 78 + ``` 79 + 80 + Example of using your own strategy with the defaults: 81 + 82 + ```ts 83 + import {stringSameness} from "@foxxmd/string-sameness"; 84 + import {ComparisonStrategy, levenStrategy, cosineStrategy, diceStrategy} from "@foxxmd/string-sameness/strategies"; 85 + 86 + const myStrat: ComparisonStrategy = { 87 + name: 'MyCoolStrat', 88 + strategy: (valA: string, valB: string) => { 89 + const a = valA.concat(valB); 90 + return a.length; 91 + }, 92 + } 93 + const strats = [ 94 + levenStrategy, 95 + cosineStrategy, 96 + diceStrategy, 97 + myStrat 98 + ] 99 + 100 + const result = stringSameness('This is one sentence', 'This is another sentence', {strategies: strats}); 101 + ``` 102 + 103 + ## Normalization 104 + 105 + Pass a list of functions using `{transforms: []}` to transform the strings before comparison. When not explicitly provided a default set of functions is applied to normalize the strings (to remove trivial differences): 41 106 42 107 * convert to lowercase 43 108 * trim (remove whitespace at beginning/end) ··· 57 122 (str) => str.replace(/[aeiou]/ig, 'e') 58 123 ] 59 124 60 - const result = stringSameness('This is one sentence', 'This is another sentence', myFuncs); 125 + const result = stringSameness('This is one sentence', 'This is another sentence', {transforms: myFuncs}); 126 + ``` 127 + 128 + ## Factory 129 + 130 + For convenience, a factory function is also provided: 131 + 132 + ```ts 133 + import {createStringSameness} from "@foxxmd/string-sameness"; 134 + import {levenStrategy} from "@foxxmd/string-sameness/strategies"; 135 + import {myTransforms, myStrats} from './util'; 136 + 137 + // sets the default object to used with the third argument for `stringSameness` 138 + const myCompare = createStringSameness({transforms: myTransforms, strategies: myStrats}); 139 + 140 + // uses myTransforms and myStrats 141 + const plainResult = myCompare('This is one sentence', 'This is another sentence'); 142 + 143 + // override your defaults using the third argument like normal 144 + const overrideResults = myCompare('This is one sentence', 'This is another sentence', {strategies: [levenStrategy]}); 61 145 ```
+36
dist/atomic.d.ts
··· 1 + export interface StringComparisonOptions { 2 + transforms?: ((str: string) => string)[]; 3 + strategies?: ComparisonStrategy[]; 4 + } 5 + export interface StringSamenessResult { 6 + strategies: { 7 + [key: string]: ComparisonStrategyResult; 8 + }; 9 + highScore: number; 10 + highScoreWeighted: number; 11 + } 12 + export interface ComparisonStrategyResultObject { 13 + score: number; 14 + [key: string]: any; 15 + } 16 + export interface ComparisonStrategyResult extends ComparisonStrategyResultObject { 17 + } 18 + export interface NamedComparisonStrategyObjectResult extends ComparisonStrategyResultObject { 19 + name: string; 20 + } 21 + export type ComparisonStrategyResultValue = number | ComparisonStrategyResultObject; 22 + export type StrategyFunc = (strA: string, strB: string) => ComparisonStrategyResultValue; 23 + export interface ComparisonStrategy { 24 + /** 25 + * The name of this strategy 26 + * */ 27 + name: string; 28 + /** 29 + * A function that accepts two string arguments and returns a number 30 + * */ 31 + strategy: StrategyFunc; 32 + /** 33 + * An optional function that accepts to string arguments and returns whether this strategy should be used 34 + * */ 35 + isValid?: (strA: string, strB: string) => boolean; 36 + }
+2
dist/atomic.js
··· 1 + "use strict"; 2 + Object.defineProperty(exports, "__esModule", { value: true });
+6 -15
dist/index.d.ts
··· 1 - export declare const defaultStrCompareTransformFuncs: ((str: string) => string)[]; 2 - export interface StringComparisonOptions { 3 - transforms?: ((str: string) => string)[]; 4 - } 5 - export interface StringSamenessResult { 6 - scores: { 7 - dice: number; 8 - cosine: number; 9 - leven: number; 10 - }; 11 - highScore: number; 12 - highScoreWeighted: number; 13 - } 14 - export declare const stringSameness: (valA: string, valB: string, options?: StringComparisonOptions) => StringSamenessResult; 15 - export default stringSameness; 1 + import { StringComparisonOptions, StringSamenessResult } from "./atomic"; 2 + declare const defaultStrCompareTransformFuncs: ((str: string) => string)[]; 3 + declare const defaultStrategies: import("./atomic").ComparisonStrategy[]; 4 + declare const stringSameness: (valA: string, valB: string, options?: StringComparisonOptions) => StringSamenessResult; 5 + declare const createStringSameness: (defaults: StringComparisonOptions) => (valA: string, valB: string, options?: StringComparisonOptions) => StringSamenessResult; 6 + export { StringSamenessResult, StringComparisonOptions, stringSameness, createStringSameness, defaultStrategies, defaultStrCompareTransformFuncs };
+37 -23
dist/index.js
··· 1 1 "use strict"; 2 - var __importDefault = (this && this.__importDefault) || function (mod) { 3 - return (mod && mod.__esModule) ? mod : { "default": mod }; 4 - }; 5 2 Object.defineProperty(exports, "__esModule", { value: true }); 6 - exports.stringSameness = exports.defaultStrCompareTransformFuncs = void 0; 7 - const cosineSimilarity_js_1 = __importDefault(require("./matchingStrategies/cosineSimilarity.js")); 8 - const levenSimilarity_js_1 = __importDefault(require("./matchingStrategies/levenSimilarity.js")); 9 - const string_similarity_1 = __importDefault(require("string-similarity")); 3 + exports.defaultStrCompareTransformFuncs = exports.defaultStrategies = exports.createStringSameness = exports.stringSameness = void 0; 4 + const matchingStrategies_1 = require("./matchingStrategies"); 10 5 const sentenceLengthWeight = (length) => { 11 6 // thanks jordan :') 12 7 // constants are black magic 13 8 return (Math.log(length) / 0.20) - 5; 14 9 }; 15 - exports.defaultStrCompareTransformFuncs = [ 10 + const defaultStrCompareTransformFuncs = [ 16 11 // lower case to remove case sensitivity 17 12 (str) => str.toLocaleLowerCase(), 18 13 // remove excess whitespace ··· 22 17 // replace all instances of 2 or more whitespace with one whitespace 23 18 (str) => str.replace(/\s{2,}|\n/g, " ") 24 19 ]; 20 + exports.defaultStrCompareTransformFuncs = defaultStrCompareTransformFuncs; 21 + const defaultStrategies = [ 22 + matchingStrategies_1.diceStrategy, 23 + matchingStrategies_1.levenStrategy, 24 + matchingStrategies_1.cosineStrategy 25 + ]; 26 + exports.defaultStrategies = defaultStrategies; 25 27 const stringSameness = (valA, valB, options) => { 26 - const { transforms = exports.defaultStrCompareTransformFuncs, } = options || {}; 28 + const { transforms = defaultStrCompareTransformFuncs, strategies = defaultStrategies, } = options || {}; 27 29 const cleanA = transforms.reduce((acc, curr) => curr(acc), valA); 28 30 const cleanB = transforms.reduce((acc, curr) => curr(acc), valB); 29 31 const shortest = cleanA.length > cleanB.length ? cleanB : cleanA; 30 - // Dice's Coefficient 31 - const dice = string_similarity_1.default.compareTwoStrings(cleanA, cleanB) * 100; 32 - // Cosine similarity 33 - const cosine = (0, cosineSimilarity_js_1.default)(cleanA, cleanB) * 100; 34 - // Levenshtein distance 35 - const ls = (0, levenSimilarity_js_1.default)(cleanA, cleanB); 36 - const levenSimilarPercent = ls[1]; 32 + const stratResults = []; 33 + for (const strat of strategies) { 34 + if (strat.isValid !== undefined && !strat.isValid(cleanA, cleanB)) { 35 + continue; 36 + } 37 + const res = strat.strategy(cleanA, cleanB); 38 + const resObj = typeof res === 'number' ? { score: res } : res; 39 + stratResults.push({ 40 + ...resObj, 41 + name: strat.name 42 + }); 43 + } 37 44 // use shortest sentence for weight 38 45 const weightScore = sentenceLengthWeight(shortest.length); 39 46 // take average score 40 - const highScore = (dice + cosine + levenSimilarPercent) / 3; 47 + const highScore = stratResults.reduce((acc, curr) => acc + curr.score, 0) / stratResults.length; 41 48 // weight score can be a max of 15 42 49 const highScoreWeighted = highScore + Math.min(weightScore, 15); 50 + const stratObj = stratResults.reduce((acc, curr) => { 51 + const { name, score, ...rest } = curr; 52 + acc[curr.name] = { 53 + ...rest, 54 + score, 55 + }; 56 + return acc; 57 + }, {}); 43 58 return { 44 - scores: { 45 - dice, 46 - cosine, 47 - leven: levenSimilarPercent 48 - }, 59 + strategies: stratObj, 49 60 highScore, 50 61 highScoreWeighted, 51 62 }; 52 63 }; 53 64 exports.stringSameness = stringSameness; 54 - exports.default = exports.stringSameness; 65 + const createStringSameness = (defaults) => { 66 + return (valA, valB, options = {}) => stringSameness(valA, valB, { ...defaults, ...options }); 67 + }; 68 + exports.createStringSameness = createStringSameness;
+3 -2
dist/matchingStrategies/cosineSimilarity.d.ts
··· 1 - declare const calculateCosineSimilarity: (strA: string, strB: string) => number; 2 - export default calculateCosineSimilarity; 1 + import { ComparisonStrategy } from "../atomic"; 2 + export declare const calculateCosineSimilarity: (strA: string, strB: string) => number; 3 + export declare const cosineStrategy: ComparisonStrategy;
+7 -2
dist/matchingStrategies/cosineSimilarity.js
··· 2 2 // reproduced from https://github.com/sumn2u/string-comparison/blob/master/jscosine.js 3 3 // https://sumn2u.medium.com/string-similarity-comparision-in-js-with-examples-4bae35f13968 4 4 Object.defineProperty(exports, "__esModule", { value: true }); 5 + exports.cosineStrategy = exports.calculateCosineSimilarity = void 0; 5 6 function termFreqMap(str) { 6 7 var words = str.split(' '); 7 8 var termFreq = {}; ··· 39 40 function cosineSimilarity(vecA, vecB) { 40 41 return vecDotProduct(vecA, vecB) / (vecMagnitude(vecA) * vecMagnitude(vecB)); 41 42 } 42 - const calculateCosineSimilarity = function textCosineSimilarity(strA, strB) { 43 + const calculateCosineSimilarity = (strA, strB) => { 43 44 var termFreqA = termFreqMap(strA); 44 45 var termFreqB = termFreqMap(strB); 45 46 var dict = {}; ··· 49 50 var termFreqVecB = termFreqMapToVector(termFreqB, dict); 50 51 return cosineSimilarity(termFreqVecA, termFreqVecB); 51 52 }; 52 - exports.default = calculateCosineSimilarity; 53 + exports.calculateCosineSimilarity = calculateCosineSimilarity; 54 + exports.cosineStrategy = { 55 + name: 'cosine', 56 + strategy: (valA, valB) => (0, exports.calculateCosineSimilarity)(valA, valB) * 100 57 + };
+3
dist/matchingStrategies/diceSimilarity.d.ts
··· 1 + import { ComparisonStrategy } from "../atomic"; 2 + export declare const calculateDiceSimilarity: (valA: string, valB: string) => number; 3 + export declare const diceStrategy: ComparisonStrategy;
+13
dist/matchingStrategies/diceSimilarity.js
··· 1 + "use strict"; 2 + var __importDefault = (this && this.__importDefault) || function (mod) { 3 + return (mod && mod.__esModule) ? mod : { "default": mod }; 4 + }; 5 + Object.defineProperty(exports, "__esModule", { value: true }); 6 + exports.diceStrategy = exports.calculateDiceSimilarity = void 0; 7 + const string_similarity_1 = __importDefault(require("string-similarity")); 8 + const calculateDiceSimilarity = (valA, valB) => string_similarity_1.default.compareTwoStrings(valA, valB); 9 + exports.calculateDiceSimilarity = calculateDiceSimilarity; 10 + exports.diceStrategy = { 11 + name: 'dice', 12 + strategy: (valA, valB) => string_similarity_1.default.compareTwoStrings(valA, valB) * 100 13 + };
+5
dist/matchingStrategies/index.d.ts
··· 1 + import { levenStrategy } from "./levenSimilarity"; 2 + import { cosineStrategy } from "./cosineSimilarity"; 3 + import { diceStrategy } from "./diceSimilarity"; 4 + import { ComparisonStrategy, ComparisonStrategyResultValue, ComparisonStrategyResultObject, StrategyFunc } from '../atomic'; 5 + export { levenStrategy, cosineStrategy, diceStrategy, ComparisonStrategy, ComparisonStrategyResultObject, ComparisonStrategyResultValue, StrategyFunc };
+9
dist/matchingStrategies/index.js
··· 1 + "use strict"; 2 + Object.defineProperty(exports, "__esModule", { value: true }); 3 + exports.diceStrategy = exports.cosineStrategy = exports.levenStrategy = void 0; 4 + const levenSimilarity_1 = require("./levenSimilarity"); 5 + Object.defineProperty(exports, "levenStrategy", { enumerable: true, get: function () { return levenSimilarity_1.levenStrategy; } }); 6 + const cosineSimilarity_1 = require("./cosineSimilarity"); 7 + Object.defineProperty(exports, "cosineStrategy", { enumerable: true, get: function () { return cosineSimilarity_1.cosineStrategy; } }); 8 + const diceSimilarity_1 = require("./diceSimilarity"); 9 + Object.defineProperty(exports, "diceStrategy", { enumerable: true, get: function () { return diceSimilarity_1.diceStrategy; } });
+3 -2
dist/matchingStrategies/levenSimilarity.d.ts
··· 1 - declare const levenSimilarity: (valA: string, valB: string) => number[]; 2 - export default levenSimilarity; 1 + import { ComparisonStrategy } from "../atomic"; 2 + export declare const calculateLevenSimilarity: (valA: string, valB: string) => number[]; 3 + export declare const levenStrategy: ComparisonStrategy;
+14 -2
dist/matchingStrategies/levenSimilarity.js
··· 3 3 return (mod && mod.__esModule) ? mod : { "default": mod }; 4 4 }; 5 5 Object.defineProperty(exports, "__esModule", { value: true }); 6 + exports.levenStrategy = exports.calculateLevenSimilarity = void 0; 6 7 const leven_1 = __importDefault(require("leven")); 7 - const levenSimilarity = (valA, valB) => { 8 + const calculateLevenSimilarity = (valA, valB) => { 8 9 let longer; 9 10 let shorter; 10 11 if (valA.length > valB.length) { ··· 19 20 const diff = (distance / longer.length) * 100; 20 21 return [distance, 100 - diff]; 21 22 }; 22 - exports.default = levenSimilarity; 23 + exports.calculateLevenSimilarity = calculateLevenSimilarity; 24 + const stratFunc = (valA, valB) => { 25 + const res = (0, exports.calculateLevenSimilarity)(valA, valB); 26 + return { 27 + distance: res[0], 28 + score: res[1] 29 + }; 30 + }; 31 + exports.levenStrategy = { 32 + name: 'leven', 33 + strategy: stratFunc 34 + };
+5 -3
package.json
··· 1 1 { 2 2 "name": "@foxxmd/string-sameness", 3 - "version": "0.1.1", 3 + "version": "0.1.2", 4 4 "description": "determine how closely the same two strings are", 5 5 "repository": "https://github.com/foxxmd/string-sameness", 6 6 "author": "FoxxMD", ··· 21 21 }, 22 22 "exports": { 23 23 ".": "./dist/index.js", 24 - "./strategies/cosine": "./dist/cosineSimilarity.js", 25 - "./strategies/leven": "./dist/levenSimilarity.js" 24 + "./strategies": "./dist/matchingStrategies/index.js", 25 + "./strategies/cosine": "./dist/matchingStrategies/cosineSimilarity.js", 26 + "./strategies/leven": "./dist/matchingStrategies/levenSimilarity.js", 27 + "./strategies/deice": "./dist/matchingStrategies/diceSimilarity.js" 26 28 }, 27 29 "devDependencies": { 28 30 "@tsconfig/node16": "^1.0.3",
+47
src/atomic.ts
··· 1 + export interface StringComparisonOptions { 2 + transforms?: ((str: string) => string)[] 3 + strategies?: ComparisonStrategy[] 4 + } 5 + 6 + export interface StringSamenessResult { 7 + strategies: { 8 + [key: string]: ComparisonStrategyResult 9 + }, 10 + highScore: number 11 + highScoreWeighted: number 12 + } 13 + 14 + export interface ComparisonStrategyResultObject { 15 + score: number 16 + 17 + [key: string]: any 18 + } 19 + 20 + export interface ComparisonStrategyResult extends ComparisonStrategyResultObject { 21 + // TODO maybe add more things in the future 22 + //scoreFormatted: string 23 + } 24 + 25 + export interface NamedComparisonStrategyObjectResult extends ComparisonStrategyResultObject { 26 + name: string 27 + } 28 + 29 + export type ComparisonStrategyResultValue = number | ComparisonStrategyResultObject; 30 + 31 + export type StrategyFunc = (strA: string, strB: string) => ComparisonStrategyResultValue; 32 + 33 + export interface ComparisonStrategy { 34 + /** 35 + * The name of this strategy 36 + * */ 37 + name: string 38 + /** 39 + * A function that accepts two string arguments and returns a number 40 + * */ 41 + strategy: StrategyFunc 42 + 43 + /** 44 + * An optional function that accepts to string arguments and returns whether this strategy should be used 45 + * */ 46 + isValid?: (strA: string, strB: string) => boolean 47 + }
+51 -33
src/index.ts
··· 1 - import calculateCosineSimilarity from "./matchingStrategies/cosineSimilarity.js"; 2 - import levenSimilarity from "./matchingStrategies/levenSimilarity.js"; 3 - import stringSimilarity from 'string-similarity'; 1 + import {cosineStrategy, levenStrategy, diceStrategy} from "./matchingStrategies"; 2 + import { 3 + ComparisonStrategyResult, 4 + NamedComparisonStrategyObjectResult, 5 + StringComparisonOptions, 6 + StringSamenessResult 7 + } from "./atomic"; 8 + 4 9 const sentenceLengthWeight = (length: number) => { 5 10 // thanks jordan :') 6 11 // constants are black magic 7 12 return (Math.log(length) / 0.20) - 5; 8 13 } 9 14 10 - export const defaultStrCompareTransformFuncs = [ 15 + const defaultStrCompareTransformFuncs = [ 11 16 // lower case to remove case sensitivity 12 17 (str: string) => str.toLocaleLowerCase(), 13 18 // remove excess whitespace ··· 18 23 (str: string) => str.replace(/\s{2,}|\n/g, " ") 19 24 ]; 20 25 21 - export interface StringComparisonOptions { 22 - transforms?: ((str: string) => string)[] 23 - } 24 - 25 - export interface StringSamenessResult { 26 - scores: { 27 - dice: number 28 - cosine: number 29 - leven: number 30 - }, 31 - highScore: number 32 - highScoreWeighted: number 33 - } 26 + const defaultStrategies = [ 27 + diceStrategy, 28 + levenStrategy, 29 + cosineStrategy 30 + ] 34 31 35 - 36 - export const stringSameness = (valA: string, valB: string, options?: StringComparisonOptions): StringSamenessResult => { 32 + const stringSameness = (valA: string, valB: string, options?: StringComparisonOptions): StringSamenessResult => { 37 33 38 34 const { 39 35 transforms = defaultStrCompareTransformFuncs, 36 + strategies = defaultStrategies, 40 37 } = options || {}; 41 38 42 39 const cleanA = transforms.reduce((acc, curr) => curr(acc), valA); ··· 44 41 45 42 const shortest = cleanA.length > cleanB.length ? cleanB : cleanA; 46 43 47 - // Dice's Coefficient 48 - const dice = stringSimilarity.compareTwoStrings(cleanA, cleanB) * 100; 49 - // Cosine similarity 50 - const cosine = calculateCosineSimilarity(cleanA, cleanB) * 100; 51 - // Levenshtein distance 52 - const ls = levenSimilarity(cleanA, cleanB); 53 - const levenSimilarPercent = ls[1]; 44 + const stratResults: NamedComparisonStrategyObjectResult[] = []; 45 + 46 + for (const strat of strategies) { 47 + if (strat.isValid !== undefined && !strat.isValid(cleanA, cleanB)) { 48 + continue; 49 + } 50 + const res = strat.strategy(cleanA, cleanB); 51 + const resObj = typeof res === 'number' ? {score: res} : res; 52 + stratResults.push({ 53 + ...resObj, 54 + name: strat.name 55 + }); 56 + } 54 57 55 58 // use shortest sentence for weight 56 59 const weightScore = sentenceLengthWeight(shortest.length); 57 60 58 61 // take average score 59 - const highScore = (dice + cosine + levenSimilarPercent) / 3; 62 + const highScore = stratResults.reduce((acc, curr) => acc + curr.score, 0) / stratResults.length; 60 63 // weight score can be a max of 15 61 64 const highScoreWeighted = highScore + Math.min(weightScore, 15); 65 + const stratObj = stratResults.reduce((acc: { [key: string]: ComparisonStrategyResult }, curr) => { 66 + const {name, score, ...rest} = curr; 67 + acc[curr.name] = { 68 + ...rest, 69 + score, 70 + }; 71 + return acc; 72 + }, {}); 62 73 return { 63 - scores: { 64 - dice, 65 - cosine, 66 - leven: levenSimilarPercent 67 - }, 74 + strategies: stratObj, 68 75 highScore, 69 76 highScoreWeighted, 70 77 } 71 78 } 72 79 73 - export default stringSameness; 80 + const createStringSameness = (defaults: StringComparisonOptions) => { 81 + return (valA: string, valB: string, options: StringComparisonOptions = {}) => stringSameness(valA, valB, {...defaults, ...options}); 82 + } 83 + 84 + export { 85 + StringSamenessResult, 86 + StringComparisonOptions, 87 + stringSameness, 88 + createStringSameness, 89 + defaultStrategies, 90 + defaultStrCompareTransformFuncs 91 + }
+9 -4
src/matchingStrategies/cosineSimilarity.ts
··· 1 1 // reproduced from https://github.com/sumn2u/string-comparison/blob/master/jscosine.js 2 2 // https://sumn2u.medium.com/string-similarity-comparision-in-js-with-examples-4bae35f13968 3 3 4 + import {ComparisonStrategy} from "../atomic"; 5 + 4 6 interface StrMap { 5 - [key: string]: number 7 + [key: string]: number 6 8 } 7 9 8 10 interface BoolMap { ··· 13 15 function termFreqMap(str: string) { 14 16 var words = str.split(' '); 15 17 var termFreq: StrMap = {}; 16 - words.forEach(function(w) { 18 + words.forEach(function (w) { 17 19 termFreq[w] = (termFreq[w] || 0) + 1; 18 20 }); 19 21 return termFreq; ··· 53 55 return vecDotProduct(vecA, vecB) / (vecMagnitude(vecA) * vecMagnitude(vecB)); 54 56 } 55 57 56 - const calculateCosineSimilarity = function textCosineSimilarity(strA: string, strB: string) { 58 + export const calculateCosineSimilarity = (strA: string, strB: string) => { 57 59 var termFreqA = termFreqMap(strA); 58 60 var termFreqB = termFreqMap(strB); 59 61 ··· 67 69 return cosineSimilarity(termFreqVecA, termFreqVecB); 68 70 } 69 71 70 - export default calculateCosineSimilarity; 72 + export const cosineStrategy: ComparisonStrategy = { 73 + name: 'cosine', 74 + strategy: (valA: string, valB: string) => calculateCosineSimilarity(valA, valB) * 100 75 + }
+8
src/matchingStrategies/diceSimilarity.ts
··· 1 + import stringSimilarity from 'string-similarity'; 2 + import {ComparisonStrategy} from "../atomic"; 3 + 4 + export const calculateDiceSimilarity = (valA: string, valB: string) => stringSimilarity.compareTwoStrings(valA, valB); 5 + export const diceStrategy: ComparisonStrategy = { 6 + name: 'dice', 7 + strategy: (valA: string, valB: string) => stringSimilarity.compareTwoStrings(valA, valB) * 100 8 + }
+14
src/matchingStrategies/index.ts
··· 1 + import {levenStrategy} from "./levenSimilarity"; 2 + import {cosineStrategy} from "./cosineSimilarity"; 3 + import {diceStrategy} from "./diceSimilarity"; 4 + import {ComparisonStrategy, ComparisonStrategyResultValue, ComparisonStrategyResultObject, StrategyFunc} from '../atomic'; 5 + 6 + export { 7 + levenStrategy, 8 + cosineStrategy, 9 + diceStrategy, 10 + ComparisonStrategy, 11 + ComparisonStrategyResultObject, 12 + ComparisonStrategyResultValue, 13 + StrategyFunc 14 + }
+14 -2
src/matchingStrategies/levenSimilarity.ts
··· 1 1 import leven from "leven"; 2 + import {ComparisonStrategy} from "../atomic"; 2 3 3 - const levenSimilarity = (valA: string, valB: string) => { 4 + export const calculateLevenSimilarity = (valA: string, valB: string) => { 4 5 let longer: string; 5 6 let shorter: string; 6 7 if (valA.length > valB.length) { ··· 16 17 return [distance, 100 - diff]; 17 18 } 18 19 19 - export default levenSimilarity; 20 + const stratFunc = (valA: string, valB: string) => { 21 + const res = calculateLevenSimilarity(valA, valB); 22 + return { 23 + distance: res[0], 24 + score: res[1] 25 + }; 26 + } 27 + 28 + export const levenStrategy: ComparisonStrategy = { 29 + name: 'leven', 30 + strategy: stratFunc 31 + }