[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.

feat: Implement string reordering

FoxxMD (Oct 26, 2023, 3:10 PM EDT) 7337c2de 13312ca1

+112 -2
+12
src/atomic.ts
··· 1 1 export interface StringComparisonOptions { 2 + /** 3 + * An array of transformations to apply to each string before comparing similarity 4 + * */ 2 5 transforms?: StringTransformFunc[] 6 + /** 7 + * An array of strategies used to score similarity. All strategies scores are combined for an average high score. 8 + * */ 3 9 strategies?: ComparisonStrategy<ComparisonStrategyResultValue>[] 10 + /** 11 + * Reorder second string so its token match order of first string as closely as possible 12 + * 13 + * Useful when only the differences in content are important, but not the order of the content 14 + * */ 15 + reorder?: boolean 4 16 } 5 17 6 18 export interface StringSamenessResult {
+39 -1
src/index.ts
··· 25 25 const { 26 26 transforms = strDefaultTransforms, 27 27 strategies = defaultStrategies, 28 + reorder = false, 28 29 } = options || {}; 29 30 30 31 const cleanA = transforms.reduce((acc, curr) => curr(acc), valA); 31 - const cleanB = transforms.reduce((acc, curr) => curr(acc), valB); 32 + let cleanB = transforms.reduce((acc, curr) => curr(acc), valB); 32 33 33 34 const shortest = cleanA.length > cleanB.length ? cleanB : cleanA; 35 + 36 + if (reorder) { 37 + // 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) 38 + // so we will reorder cleanB so its tokens match the order or tokens in cleanA as closely as possible 39 + // before we run strategies 40 + cleanB = reorderStr(cleanA, cleanB); 41 + } 34 42 35 43 const stratResults: NamedComparisonStrategyObjectResult[] = []; 36 44 ··· 66 74 highScore, 67 75 highScoreWeighted, 68 76 } 77 + } 78 + 79 + export const reorderStr = (cleanA: string, cleanB: string, options?: StringComparisonOptions): string => { 80 + // to do the reordering we will use stringSameness with the provided strats to match against each token in cleanA and choose the closest token in cleanB 81 + // and add the end concat any remaining tokens from cleanB to the reordered string 82 + const aTokens = cleanA.split(' '); 83 + const bTokens = cleanB.split(' '); 84 + const orderedCandidateTokens = aTokens.reduce((acc: { ordered: string[], remaining: string[] }, curr) => { 85 + let highScore = 0; 86 + let highIndex: undefined | number = 0; 87 + let index = 0; 88 + for (const token of acc.remaining) { 89 + const result = stringSameness(curr, token, {...options, reorder: false}); 90 + if (result.highScore > highScore) { 91 + highScore = result.highScore; 92 + highIndex = index; 93 + } 94 + index++; 95 + } 96 + 97 + const splicedRemaining = [...acc.remaining]; 98 + if(highIndex <= splicedRemaining.length - 1) { 99 + splicedRemaining.splice(highIndex, 1); 100 + } 101 + const ordered = highIndex <= acc.remaining.length - 1 ? acc.ordered.concat(acc.remaining[highIndex]) : acc.ordered; 102 + 103 + return {ordered: ordered, remaining: splicedRemaining}; 104 + }, {ordered: [], remaining: bTokens}); 105 + const allOrderedCandidateTokens = orderedCandidateTokens.ordered.concat(orderedCandidateTokens.remaining); 106 + return allOrderedCandidateTokens.join(' '); 69 107 } 70 108 71 109 const createStringSameness = (defaults: StringComparisonOptions) => {
+61 -1
tests/index.test.ts
··· 4 4 stringSameness, 5 5 defaultStrCompareTransformFuncs as transforms, 6 6 strategies, 7 - defaultStrategies 7 + reorderStr 8 8 } from '../src/index.js'; 9 9 10 10 const sameString = 'this string is the same'; ··· 74 74 it(`${name}: strings are close but not identical`, function() { 75 75 const res = strat.strategy('Another Brick in the Wall, Pt. 1', 'Another Brick in the Wall, Pt. 2'); 76 76 assert.isAtMost(res.score, 99); 77 + assert.isAtLeast(res.score, 80); 77 78 }); 78 79 } 79 80 }); ··· 90 91 }); 91 92 92 93 }); 94 + 95 + 96 + describe('String reordering', () => { 97 + 98 + it('should reorder exact strings', function () { 99 + 100 + const exact = [ 101 + ['this is correct order', 'order correct this is'], 102 + ['we have a few it he close words', 'close it he a words have we few'], 103 + ] 104 + 105 + for(const test of exact) { 106 + assert.equal(reorderStr(test[0], test[1]), test[0]) 107 + } 108 + }); 109 + 110 + it('should reorder strings as closely as possible', function () { 111 + 112 + const a = 'this is correct order'; 113 + const b = 'order correct this is and extra'; 114 + const expected = 'this is correct order and extra' 115 + 116 + assert.equal(reorderStr(a, b), expected); 117 + 118 + const aLong = 'this vorrect order with more tokens'; 119 + const bLong = 'order correct this extra'; 120 + const expectedLong = 'this correct order extra'; 121 + 122 + assert.equal(reorderStr(aLong, bLong), expectedLong); 123 + 124 + }); 125 + 126 + }); 127 + 128 + describe('Sameness functionality', function() { 129 + 130 + describe('Sanity Checks', function() { 131 + it(`scores same strings as identical`, function() { 132 + const res = stringSameness(sameString, sameString); 133 + assert.equal(res.highScore, 100); 134 + }); 135 + 136 + it(`scores similar strings highly but not as identical`, function() { 137 + const res = stringSameness('Another Brick in the Wall, Pt. 1', 'Another Brick in the Wall, Pt. 2'); 138 + assert.isAtMost(res.highScore, 99); 139 + assert.isAtLeast(res.highScore, 95); 140 + }); 141 + 142 + it(`scores completely different strings as near zero`, function() { 143 + const res = stringSameness(sameString, 'pay bull blood for voice'); 144 + assert.isAtMost(res.highScore, 10); 145 + }); 146 + }); 147 + 148 + it('should score reordered exact strings as identical', function () { 149 + const res = stringSameness('this is correct order', 'order correct this is', {reorder: true}); 150 + assert.equal(res.highScore, 100); 151 + }); 152 + });