diff --git a/lib/natural/distance/levenshtein_distance.js b/lib/natural/distance/levenshtein_distance.js index 291d70876..a2ce32b01 100644 --- a/lib/natural/distance/levenshtein_distance.js +++ b/lib/natural/distance/levenshtein_distance.js @@ -218,13 +218,16 @@ function levenshteinDistance (source, target, options) { distanceMatrix[row][column] = { cost: minCostParent.cost, parentCell: minCostParent.coordinates } - if (isUnrestrictedDamerau) { - lastRowMap[sourceElement] = row - if (sourceElement === targetElement) { - lastColMatch = column - } + if (isUnrestrictedDamerau && sourceElement === targetElement) { + lastColMatch = column } } + // da[c]: last row in which source character c occurred. Update after the + // inner loop so the transposition lookup only references earlier rows; the + // per-column update gave wrong distances for repeated characters (issue #757). + if (isUnrestrictedDamerau) { + lastRowMap[source[row - 1]] = row + } } if (!options.search) { diff --git a/spec/damerau_levenshtein_spec.ts b/spec/damerau_levenshtein_spec.ts index 9dcbca670..140425718 100644 --- a/spec/damerau_levenshtein_spec.ts +++ b/spec/damerau_levenshtein_spec.ts @@ -25,6 +25,11 @@ describe('DamerauLevenshtein', function () { expect(DamerauLevenshteinDistance('CA', 'ABC')).toBe(2) expect(DamerauLevenshteinDistance('a cat', 'a abct')).toBe(2) }) + + it('should stay symmetric for strings with repeated characters (issue #757)', function () { + expect(DamerauLevenshteinDistance('0,1,10,11', '0,11,110,111')).toBe(3) + expect(DamerauLevenshteinDistance('0,11,110,111', '0,1,10,11')).toBe(3) + }) }) describe('options.restricted = true', function () {