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

fix: Fix reorder variant string length issues

Depending on the order strings were given the recombined, ordered strings could have null/undefined in them due to bad logic. Refactor entire approach to be parameter order invariant.

FoxxMD (Jan 30, 2024, 3:55 PM EST) 7e032caa 85008261

+85 -27
+9
src/atomic.ts
··· 13 13 * Useful when only the differences in content are important, but not the order of the content 14 14 * */ 15 15 reorder?: boolean 16 + 17 + /** 18 + * When `reorder` is used this determines how to split each string into the tokens that will be reordered. 19 + * 20 + * 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 21 + * 22 + * @default " " 23 + * */ 24 + delimiter?: string | RegExp 16 25 } 17 26 18 27 export interface StringSamenessResult {
+67 -23
src/index.ts
··· 26 26 transforms = strDefaultTransforms, 27 27 strategies = defaultStrategies, 28 28 reorder = false, 29 + delimiter = ' ' 29 30 } = options || {}; 30 31 31 - const cleanA = transforms.reduce((acc, curr) => curr(acc), valA); 32 + let cleanA = transforms.reduce((acc, curr) => curr(acc), valA); 32 33 let cleanB = transforms.reduce((acc, curr) => curr(acc), valB); 33 34 34 - const shortest = cleanA.length > cleanB.length ? cleanB : cleanA; 35 - 36 35 if (reorder) { 37 36 // 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 37 + // 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 39 38 // before we run strategies 40 - cleanB = reorderStr(cleanA, cleanB); 39 + const [orderedX, orderedY] = reorderStr(cleanA, cleanB); 40 + cleanA = orderedX; 41 + cleanB = orderedY; 41 42 } 43 + 44 + const shortest = cleanA.length > cleanB.length ? cleanB : cleanA; 42 45 43 46 const stratResults: NamedComparisonStrategyObjectResult[] = []; 44 47 ··· 76 79 } 77 80 } 78 81 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) => { 82 + export const reorderStr = (strA: string, strB: string, options?: StringComparisonOptions): [string, string] => { 83 + 84 + const { 85 + transforms = strDefaultTransforms, 86 + strategies = defaultStrategies, 87 + delimiter = ' ' 88 + } = options || {}; 89 + 90 + const cleanA = transforms.reduce((acc, curr) => curr(acc), strA); 91 + const cleanB = transforms.reduce((acc, curr) => curr(acc), strB); 92 + 93 + // split by "token" 94 + const eTokens = cleanA.split(delimiter); 95 + const cTokens = cleanB.split(delimiter); 96 + 97 + 98 + let longerTokens: string[], 99 + shorterTokens: string[]; 100 + 101 + if (eTokens.length > cTokens.length) { 102 + longerTokens = eTokens; 103 + shorterTokens = cTokens; 104 + } else { 105 + longerTokens = cTokens; 106 + shorterTokens = eTokens; 107 + } 108 + 109 + // we will use longest string (token list) as the reducer and order the shorter list to match it 110 + // so we don't have to deal with undefined positions in the shorter list 111 + 112 + const orderedCandidateTokens = longerTokens.reduce((acc: { ordered: string[], remaining: string[] }, curr) => { 113 + // if we've run out of tokens in the shorter list just return 114 + if (acc.remaining.length === 0) { 115 + return acc; 116 + } 117 + 118 + // on each iteration of tokens in the long list 119 + // we iterate through remaining tokens from the shorter list and find the token with the most sameness 120 + 85 121 let highScore = 0; 86 - let highIndex: undefined | number = 0; 122 + let highIndex = 0; 87 123 let index = 0; 88 124 for (const token of acc.remaining) { 89 - const result = stringSameness(curr, token, {...options, reorder: false}); 90 - if (result.highScore > highScore) { 91 - highScore = result.highScore; 125 + const result = stringSameness(curr, token, {strategies}); 126 + if (result.highScoreWeighted > highScore) { 127 + highScore = result.highScoreWeighted; 92 128 highIndex = index; 93 129 } 94 130 index++; 95 131 } 96 132 133 + // then remove the most same token from the remaining short list tokens 97 134 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; 135 + splicedRemaining.splice(highIndex, 1); 102 136 103 - return {ordered: ordered, remaining: splicedRemaining}; 104 - }, {ordered: [], remaining: bTokens}); 105 - const allOrderedCandidateTokens = orderedCandidateTokens.ordered.concat(orderedCandidateTokens.remaining); 106 - return allOrderedCandidateTokens.join(' '); 137 + return { 138 + // finally add the most same token to the ordered short list 139 + ordered: acc.ordered.concat(acc.remaining[highIndex]), 140 + // and return the remaining short list tokens 141 + remaining: splicedRemaining 142 + }; 143 + }, { 144 + // "ordered" is the result of ordering tokens in the shorter list to match longer token order 145 + ordered: [], 146 + // remaining is the initial shorter list 147 + remaining: shorterTokens 148 + }); 149 + 150 + return [longerTokens.join(' '), orderedCandidateTokens.ordered.join(' ')]; 107 151 } 108 152 109 153 const createStringSameness = (defaults: StringComparisonOptions) => {
+9 -4
tests/index.test.ts
··· 103 103 ] 104 104 105 105 for(const test of exact) { 106 - assert.equal(reorderStr(test[0], test[1]), test[0]) 106 + const res = reorderStr(test[0], test[1]); 107 + assert.equal(res[1], test[1]) 107 108 } 108 109 }); 109 110 ··· 111 112 112 113 const a = 'this is correct order'; 113 114 const b = 'order correct this is and extra'; 114 - const expected = 'this is correct order and extra' 115 + const expected = 'order correct this is' 116 + 117 + const res = reorderStr(a, b); 115 118 116 - assert.equal(reorderStr(a, b), expected); 119 + assert.equal(res[1], expected); 117 120 118 121 const aLong = 'this vorrect order with more tokens'; 119 122 const bLong = 'order correct this extra'; 120 123 const expectedLong = 'this correct order extra'; 121 124 122 - assert.equal(reorderStr(aLong, bLong), expectedLong); 125 + const res2 = reorderStr(aLong, bLong); 126 + 127 + assert.equal(res2[1], expectedLong); 123 128 124 129 }); 125 130