···11-export interface StringComparisonOptions {
22- /**
33- * An array of transformations to apply to each string before comparing similarity
44- * */
55- transforms?: StringTransformFunc[];
66- /**
77- * An array of strategies used to score similarity. All strategies scores are combined for an average high score.
88- * */
99- strategies?: ComparisonStrategy<ComparisonStrategyResultValue>[];
1010- /**
1111- * Reorder second string so its token match order of first string as closely as possible
1212- *
1313- * Useful when only the differences in content are important, but not the order of the content
1414- * */
1515- reorder?: boolean;
1616- /**
1717- * When `reorder` is used this determines how to split each string into the tokens that will be reordered.
1818- *
1919- * The value of this property is used in String.split() -- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split#separator
2020- *
2121- * @default " "
2222- * */
2323- delimiter?: string | RegExp;
2424-}
2525-export interface StringSamenessResult {
2626- strategies: {
2727- [key: string]: ComparisonStrategyResult;
2828- };
2929- highScore: number;
3030- highScoreWeighted: number;
3131-}
3232-export interface ComparisonStrategyResultObject {
3333- /**
3434- * The normalized (0 to 100) score of this comparison
3535- * */
3636- score: number;
3737- /**
3838- * The raw value returned by the comparison (not normalized)
3939- * */
4040- rawScore?: number;
4141- [key: string]: any;
4242-}
4343-export interface ComparisonStrategyResult extends ComparisonStrategyResultObject {
4444-}
4545-export interface NamedComparisonStrategyObjectResult extends ComparisonStrategyResultObject {
4646- name: string;
4747-}
4848-export type ComparisonStrategyResultValue = number | ComparisonStrategyResultObject;
4949-export type StrategyFunc<T extends ComparisonStrategyResultValue> = (strA: string, strB: string) => T;
5050-export interface ComparisonStrategy<T extends ComparisonStrategyResultValue> {
5151- /**
5252- * The name of this strategy
5353- * */
5454- name: string;
5555- /**
5656- * A function that accepts two string arguments and returns a number
5757- * */
5858- strategy: StrategyFunc<T>;
5959- /**
6060- * An optional function that accepts two string arguments and returns whether this strategy should be used
6161- * */
6262- isValid?: (strA: string, strB: string) => boolean;
6363-}
6464-export type StringTransformFunc = (str: string) => string;
···11-import { ComparisonStrategy, ComparisonStrategyResultObject } from "../atomic.js";
22-export declare const calculateCosineSimilarity: (strA: string, strB: string) => number;
33-/**
44- * Compares whole tokens (words) within a string independent of order
55- *
66- * This strategy is automatically disabled for strings with less than 4 words because it can lead to inaccurate scores due to not comparing characters IE it is not very useful for short sentences and comparing single words with typos
77- *
88- * If you'd like to use it even in these scenarios build your own strategy array using cosineStrategyAggressive instead of this one
99- * */
1010-export declare const cosineStrategy: ComparisonStrategy<ComparisonStrategyResultObject>;
1111-/**
1212- * Always runs (strings are always valid) which may lead to inaccurate scores in low token-count strings
1313- * */
1414-export declare const cosineStrategyAggressive: ComparisonStrategy<ComparisonStrategyResultObject>;
···11-"use strict";
22-// reproduced from https://github.com/sumn2u/string-comparison/blob/master/jscosine.js
33-// https://sumn2u.medium.com/string-similarity-comparision-in-js-with-examples-4bae35f13968
44-Object.defineProperty(exports, "__esModule", { value: true });
55-exports.cosineStrategyAggressive = exports.cosineStrategy = exports.calculateCosineSimilarity = void 0;
66-function termFreqMap(str) {
77- var words = str.split(' ');
88- var termFreq = {};
99- words.forEach(function (w) {
1010- termFreq[w] = (termFreq[w] || 0) + 1;
1111- });
1212- return termFreq;
1313-}
1414-function addKeysToDict(map, dict) {
1515- for (var key in map) {
1616- dict[key] = true;
1717- }
1818-}
1919-function termFreqMapToVector(map, dict) {
2020- var termFreqVector = [];
2121- for (var term in dict) {
2222- termFreqVector.push(map[term] || 0);
2323- }
2424- return termFreqVector;
2525-}
2626-function vecDotProduct(vecA, vecB) {
2727- var product = 0;
2828- for (var i = 0; i < vecA.length; i++) {
2929- product += vecA[i] * vecB[i];
3030- }
3131- return product;
3232-}
3333-function vecMagnitude(vec) {
3434- var sum = 0;
3535- for (var i = 0; i < vec.length; i++) {
3636- sum += vec[i] * vec[i];
3737- }
3838- return Math.sqrt(sum);
3939-}
4040-function cosineSimilarity(vecA, vecB) {
4141- return vecDotProduct(vecA, vecB) / (vecMagnitude(vecA) * vecMagnitude(vecB));
4242-}
4343-const calculateCosineSimilarity = (strA, strB) => {
4444- var termFreqA = termFreqMap(strA);
4545- var termFreqB = termFreqMap(strB);
4646- var dict = {};
4747- addKeysToDict(termFreqA, dict);
4848- addKeysToDict(termFreqB, dict);
4949- var termFreqVecA = termFreqMapToVector(termFreqA, dict);
5050- var termFreqVecB = termFreqMapToVector(termFreqB, dict);
5151- return cosineSimilarity(termFreqVecA, termFreqVecB);
5252-};
5353-exports.calculateCosineSimilarity = calculateCosineSimilarity;
5454-const cosineBaseStrategy = {
5555- name: 'cosine',
5656- strategy: (valA, valB) => {
5757- let res = (0, exports.calculateCosineSimilarity)(valA, valB);
5858- if (res > 0.99999) {
5959- res = 1;
6060- }
6161- return {
6262- score: res * 100,
6363- rawScore: res
6464- };
6565- }
6666-};
6767-/**
6868- * Compares whole tokens (words) within a string independent of order
6969- *
7070- * This strategy is automatically disabled for strings with less than 4 words because it can lead to inaccurate scores due to not comparing characters IE it is not very useful for short sentences and comparing single words with typos
7171- *
7272- * If you'd like to use it even in these scenarios build your own strategy array using cosineStrategyAggressive instead of this one
7373- * */
7474-exports.cosineStrategy = {
7575- ...cosineBaseStrategy,
7676- isValid: (valA, valB) => {
7777- // cosine only compares full tokens (words), rather than characters, in a string
7878- // which makes its score very inaccurate when comparing low token-count strings (short sentences and/or words with typos)
7979- // so disable its usage if there are less than 4 tokens
8080- const valATokenLength = valA.split(' ').length;
8181- const valBTokenLength = valB.split(' ').length;
8282- return valATokenLength < 4 || valBTokenLength < 4;
8383- }
8484-};
8585-/**
8686- * Always runs (strings are always valid) which may lead to inaccurate scores in low token-count strings
8787- * */
8888-exports.cosineStrategyAggressive = {
8989- ...cosineBaseStrategy
9090-};
9191-//# sourceMappingURL=cosineSimilarity.js.map
···11-export interface StringComparisonOptions {
22- /**
33- * An array of transformations to apply to each string before comparing similarity
44- * */
55- transforms?: StringTransformFunc[];
66- /**
77- * An array of strategies used to score similarity. All strategies scores are combined for an average high score.
88- * */
99- strategies?: ComparisonStrategy<ComparisonStrategyResultValue>[];
1010- /**
1111- * Reorder second string so its token match order of first string as closely as possible
1212- *
1313- * Useful when only the differences in content are important, but not the order of the content
1414- * */
1515- reorder?: boolean;
1616- /**
1717- * When `reorder` is used this determines how to split each string into the tokens that will be reordered.
1818- *
1919- * The value of this property is used in String.split() -- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split#separator
2020- *
2121- * @default " "
2222- * */
2323- delimiter?: string | RegExp;
2424-}
2525-export interface StringSamenessResult {
2626- strategies: {
2727- [key: string]: ComparisonStrategyResult;
2828- };
2929- highScore: number;
3030- highScoreWeighted: number;
3131-}
3232-export interface ComparisonStrategyResultObject {
3333- /**
3434- * The normalized (0 to 100) score of this comparison
3535- * */
3636- score: number;
3737- /**
3838- * The raw value returned by the comparison (not normalized)
3939- * */
4040- rawScore?: number;
4141- [key: string]: any;
4242-}
4343-export interface ComparisonStrategyResult extends ComparisonStrategyResultObject {
4444-}
4545-export interface NamedComparisonStrategyObjectResult extends ComparisonStrategyResultObject {
4646- name: string;
4747-}
4848-export type ComparisonStrategyResultValue = number | ComparisonStrategyResultObject;
4949-export type StrategyFunc<T extends ComparisonStrategyResultValue> = (strA: string, strB: string) => T;
5050-export interface ComparisonStrategy<T extends ComparisonStrategyResultValue> {
5151- /**
5252- * The name of this strategy
5353- * */
5454- name: string;
5555- /**
5656- * A function that accepts two string arguments and returns a number
5757- * */
5858- strategy: StrategyFunc<T>;
5959- /**
6060- * An optional function that accepts two string arguments and returns whether this strategy should be used
6161- * */
6262- isValid?: (strA: string, strB: string) => boolean;
6363-}
6464-export type StringTransformFunc = (str: string) => string;
···11-import { cosineStrategy, levenStrategy, diceStrategy, cosineStrategyAggressive } from "./matchingStrategies/index.js";
22-import { strDefaultTransforms, transforms } from "./normalization/index.js";
33-const sentenceLengthWeight = (length) => {
44- // thanks jordan :')
55- // constants are black magic
66- return (Math.log(length) / 0.20) - 5;
77-};
88-const defaultStrategies = [
99- diceStrategy,
1010- levenStrategy,
1111- cosineStrategy
1212-];
1313-const stringSameness = (valA, valB, options) => {
1414- const { transforms = strDefaultTransforms, strategies = defaultStrategies, reorder = false, delimiter = ' ' } = options || {};
1515- let cleanA = transforms.reduce((acc, curr) => curr(acc), valA);
1616- let cleanB = transforms.reduce((acc, curr) => curr(acc), valB);
1717- if (reorder) {
1818- // we want to ignore order of tokens as much as possible (user does not care about differences in word order, just absolute differences in characters overall)
1919- // so we will reorder the shorter of the two strings so its tokens match the order of tokens in the longer string as closely as possible
2020- // before we run strategies
2121- const [orderedX, orderedY] = reorderStr(cleanA, cleanB);
2222- cleanA = orderedX;
2323- cleanB = orderedY;
2424- }
2525- const shortest = cleanA.length > cleanB.length ? cleanB : cleanA;
2626- const stratResults = [];
2727- for (const strat of strategies) {
2828- if (strat.isValid !== undefined && !strat.isValid(cleanA, cleanB)) {
2929- continue;
3030- }
3131- const res = strat.strategy(cleanA, cleanB);
3232- const resObj = typeof res === 'number' ? { score: res } : res;
3333- stratResults.push({
3434- ...resObj,
3535- name: strat.name
3636- });
3737- }
3838- // use shortest sentence for weight
3939- const weightScore = sentenceLengthWeight(shortest.length);
4040- // take average score
4141- const highScore = stratResults.reduce((acc, curr) => acc + curr.score, 0) / stratResults.length;
4242- // weight score can be a max of 15
4343- const highScoreWeighted = highScore + Math.min(weightScore, 15);
4444- const stratObj = stratResults.reduce((acc, curr) => {
4545- const { name, score, ...rest } = curr;
4646- acc[curr.name] = {
4747- ...rest,
4848- score,
4949- };
5050- return acc;
5151- }, {});
5252- return {
5353- strategies: stratObj,
5454- highScore,
5555- highScoreWeighted,
5656- };
5757-};
5858-export const reorderStr = (strA, strB, options) => {
5959- const { transforms = strDefaultTransforms, strategies = defaultStrategies, delimiter = ' ' } = options || {};
6060- const cleanA = transforms.reduce((acc, curr) => curr(acc), strA);
6161- const cleanB = transforms.reduce((acc, curr) => curr(acc), strB);
6262- // split by "token"
6363- const eTokens = cleanA.split(delimiter);
6464- const cTokens = cleanB.split(delimiter);
6565- let longerTokens, shorterTokens;
6666- if (eTokens.length > cTokens.length) {
6767- longerTokens = eTokens;
6868- shorterTokens = cTokens;
6969- }
7070- else {
7171- longerTokens = cTokens;
7272- shorterTokens = eTokens;
7373- }
7474- // we will use longest string (token list) as the reducer and order the shorter list to match it
7575- // so we don't have to deal with undefined positions in the shorter list
7676- const orderedCandidateTokens = longerTokens.reduce((acc, curr) => {
7777- // if we've run out of tokens in the shorter list just return
7878- if (acc.remaining.length === 0) {
7979- return acc;
8080- }
8181- // on each iteration of tokens in the long list
8282- // we iterate through remaining tokens from the shorter list and find the token with the most sameness
8383- let highScore = 0;
8484- let highIndex = 0;
8585- let index = 0;
8686- for (const token of acc.remaining) {
8787- const result = stringSameness(curr, token, { strategies });
8888- if (result.highScoreWeighted > highScore) {
8989- highScore = result.highScoreWeighted;
9090- highIndex = index;
9191- }
9292- index++;
9393- }
9494- // then remove the most same token from the remaining short list tokens
9595- const splicedRemaining = [...acc.remaining];
9696- splicedRemaining.splice(highIndex, 1);
9797- return {
9898- // finally add the most same token to the ordered short list
9999- ordered: acc.ordered.concat(acc.remaining[highIndex]),
100100- // and return the remaining short list tokens
101101- remaining: splicedRemaining
102102- };
103103- }, {
104104- // "ordered" is the result of ordering tokens in the shorter list to match longer token order
105105- ordered: [],
106106- // remaining is the initial shorter list
107107- remaining: shorterTokens
108108- });
109109- return [longerTokens.join(' '), orderedCandidateTokens.ordered.join(' ')];
110110-};
111111-const createStringSameness = (defaults) => {
112112- return (valA, valB, options = {}) => stringSameness(valA, valB, { ...defaults, ...options });
113113-};
114114-const strategies = {
115115- diceStrategy,
116116- levenStrategy,
117117- cosineStrategy,
118118- cosineStrategyAggressive
119119-};
120120-// maintaining compatibility
121121-const defaultStrCompareTransformFuncs = strDefaultTransforms;
122122-export { stringSameness, createStringSameness, defaultStrategies, strategies, transforms, defaultStrCompareTransformFuncs, strDefaultTransforms };
123123-//# sourceMappingURL=index.js.map
···11-import { ComparisonStrategy, ComparisonStrategyResultObject } from "../atomic.js";
22-export declare const calculateCosineSimilarity: (strA: string, strB: string) => number;
33-/**
44- * Compares whole tokens (words) within a string independent of order
55- *
66- * This strategy is automatically disabled for strings with less than 4 words because it can lead to inaccurate scores due to not comparing characters IE it is not very useful for short sentences and comparing single words with typos
77- *
88- * If you'd like to use it even in these scenarios build your own strategy array using cosineStrategyAggressive instead of this one
99- * */
1010-export declare const cosineStrategy: ComparisonStrategy<ComparisonStrategyResultObject>;
1111-/**
1212- * Always runs (strings are always valid) which may lead to inaccurate scores in low token-count strings
1313- * */
1414-export declare const cosineStrategyAggressive: ComparisonStrategy<ComparisonStrategyResultObject>;
-87
dist/esm/matchingStrategies/cosineSimilarity.js
···11-// reproduced from https://github.com/sumn2u/string-comparison/blob/master/jscosine.js
22-// https://sumn2u.medium.com/string-similarity-comparision-in-js-with-examples-4bae35f13968
33-function termFreqMap(str) {
44- var words = str.split(' ');
55- var termFreq = {};
66- words.forEach(function (w) {
77- termFreq[w] = (termFreq[w] || 0) + 1;
88- });
99- return termFreq;
1010-}
1111-function addKeysToDict(map, dict) {
1212- for (var key in map) {
1313- dict[key] = true;
1414- }
1515-}
1616-function termFreqMapToVector(map, dict) {
1717- var termFreqVector = [];
1818- for (var term in dict) {
1919- termFreqVector.push(map[term] || 0);
2020- }
2121- return termFreqVector;
2222-}
2323-function vecDotProduct(vecA, vecB) {
2424- var product = 0;
2525- for (var i = 0; i < vecA.length; i++) {
2626- product += vecA[i] * vecB[i];
2727- }
2828- return product;
2929-}
3030-function vecMagnitude(vec) {
3131- var sum = 0;
3232- for (var i = 0; i < vec.length; i++) {
3333- sum += vec[i] * vec[i];
3434- }
3535- return Math.sqrt(sum);
3636-}
3737-function cosineSimilarity(vecA, vecB) {
3838- return vecDotProduct(vecA, vecB) / (vecMagnitude(vecA) * vecMagnitude(vecB));
3939-}
4040-export const calculateCosineSimilarity = (strA, strB) => {
4141- var termFreqA = termFreqMap(strA);
4242- var termFreqB = termFreqMap(strB);
4343- var dict = {};
4444- addKeysToDict(termFreqA, dict);
4545- addKeysToDict(termFreqB, dict);
4646- var termFreqVecA = termFreqMapToVector(termFreqA, dict);
4747- var termFreqVecB = termFreqMapToVector(termFreqB, dict);
4848- return cosineSimilarity(termFreqVecA, termFreqVecB);
4949-};
5050-const cosineBaseStrategy = {
5151- name: 'cosine',
5252- strategy: (valA, valB) => {
5353- let res = calculateCosineSimilarity(valA, valB);
5454- if (res > 0.99999) {
5555- res = 1;
5656- }
5757- return {
5858- score: res * 100,
5959- rawScore: res
6060- };
6161- }
6262-};
6363-/**
6464- * Compares whole tokens (words) within a string independent of order
6565- *
6666- * This strategy is automatically disabled for strings with less than 4 words because it can lead to inaccurate scores due to not comparing characters IE it is not very useful for short sentences and comparing single words with typos
6767- *
6868- * If you'd like to use it even in these scenarios build your own strategy array using cosineStrategyAggressive instead of this one
6969- * */
7070-export const cosineStrategy = {
7171- ...cosineBaseStrategy,
7272- isValid: (valA, valB) => {
7373- // cosine only compares full tokens (words), rather than characters, in a string
7474- // which makes its score very inaccurate when comparing low token-count strings (short sentences and/or words with typos)
7575- // so disable its usage if there are less than 4 tokens
7676- const valATokenLength = valA.split(' ').length;
7777- const valBTokenLength = valB.split(' ').length;
7878- return valATokenLength < 4 || valBTokenLength < 4;
7979- }
8080-};
8181-/**
8282- * Always runs (strings are always valid) which may lead to inaccurate scores in low token-count strings
8383- * */
8484-export const cosineStrategyAggressive = {
8585- ...cosineBaseStrategy
8686-};
8787-//# sourceMappingURL=cosineSimilarity.js.map