diff --git a/src/algorithms/string/levenshtein-distance/__test__/levenshteinDistance.test.js b/src/algorithms/string/levenshtein-distance/__test__/levenshteinDistance.test.js new file mode 100644 index 00000000..e4e46d56 --- /dev/null +++ b/src/algorithms/string/levenshtein-distance/__test__/levenshteinDistance.test.js @@ -0,0 +1,29 @@ +import levenshteinDistance from '../levenshteinDistance'; + +describe('levenshteinDistance', () => { + it('should calculate edit distance between two strings', () => { + expect(levenshteinDistance('', '')).toBe(0); + expect(levenshteinDistance('a', '')).toBe(1); + expect(levenshteinDistance('', 'a')).toBe(1); + expect(levenshteinDistance('abc', '')).toBe(3); + expect(levenshteinDistance('', 'abc')).toBe(3); + + // Should just add I to the beginning. + expect(levenshteinDistance('islander', 'slander')).toBe(1); + + // Needs to substitute M by K, T by M and add an A to the end + expect(levenshteinDistance('mart', 'karma')).toBe(3); + + // Substitute K by S, E by I and insert G at the end. + expect(levenshteinDistance('kitten', 'sitting')).toBe(3); + + // Should add 4 letters FOOT at the beginning. + expect(levenshteinDistance('ball', 'football')).toBe(4); + + // Should delete 4 letters FOOT at the beginning. + expect(levenshteinDistance('football', 'foot')).toBe(4); + + // Needs to substitute the first 5 chars: INTEN by EXECU + expect(levenshteinDistance('intention', 'execution')).toBe(5); + }); +}); diff --git a/src/algorithms/string/levenshtein-distance/levenshteinDistance.js b/src/algorithms/string/levenshtein-distance/levenshteinDistance.js new file mode 100644 index 00000000..051e080e --- /dev/null +++ b/src/algorithms/string/levenshtein-distance/levenshteinDistance.js @@ -0,0 +1,37 @@ +/** + * @param {string} a + * @param {string} b + * @return {number} + */ +export default function levenshteinDistance(a, b) { + // Create empty edit distance matrix for all possible modifications of + // substrings of a to substrings of b. + const distanceMatrix = Array(b.length + 1).fill(null).map(() => Array(a.length + 1).fill(null)); + + // Fill the first raw of the matrix. + // If this is first row then we're transforming empty string to a. + // In this case the number of transformations equals to size of a substring. + for (let i = 0; i <= a.length; i += 1) { + distanceMatrix[0][i] = i; + } + + // Fill the first column of the matrix. + // If this is first column then we're transforming empty string to b. + // In this case the number of transformations equals to size of b substring. + for (let j = 0; j <= b.length; j += 1) { + distanceMatrix[j][0] = j; + } + + for (let j = 1; j <= b.length; j += 1) { + for (let i = 1; i <= a.length; i += 1) { + const indicator = a[i - 1] === b[j - 1] ? 0 : 1; + distanceMatrix[j][i] = Math.min( + distanceMatrix[j][i - 1] + 1, // deletion + distanceMatrix[j - 1][i] + 1, // insertion + distanceMatrix[j - 1][i - 1] + indicator, // substitution + ); + } + } + + return distanceMatrix[b.length][a.length]; +}