diff --git a/Sources/ArgumentParser/Utilities/StringExtensions.swift b/Sources/ArgumentParser/Utilities/StringExtensions.swift index 4e6fa91b9..46413052c 100644 --- a/Sources/ArgumentParser/Utilities/StringExtensions.swift +++ b/Sources/ArgumentParser/Utilities/StringExtensions.swift @@ -141,30 +141,86 @@ extension StringProtocol where SubSequence == Substring { return Swift.max(rows, columns) } - var matrix = Array(repeating: Array(repeating: 0, count: columns + 1), count: rows + 1) - - for row in 1...rows { - matrix[row][0] = row + // Trim common prefix and suffix + var selfStartTrim = self.startIndex + var targetStartTrim = target.startIndex + while selfStartTrim < self.endIndex && + targetStartTrim < target.endIndex && + self[selfStartTrim] == target[targetStartTrim] { + self.formIndex(after: &selfStartTrim) + target.formIndex(after: &targetStartTrim) + } + + var selfEndTrim = self.endIndex + var targetEndTrim = target.endIndex + + while selfEndTrim > selfStartTrim && + targetEndTrim > targetStartTrim { + let selfIdx = self.index(before: selfEndTrim) + let targetIdx = target.index(before: targetEndTrim) + + guard self[selfIdx] == target[targetIdx] else { + break + } + + selfEndTrim = selfIdx + targetEndTrim = targetIdx } - for column in 1...columns { - matrix[0][column] = column + + // Equal strings + guard !(selfStartTrim == self.endIndex && + targetStartTrim == target.endIndex) else { + return 0 } - for row in 1...rows { - for column in 1...columns { - let source = self[self.index(self.startIndex, offsetBy: row - 1)] - let target = target[target.index(target.startIndex, offsetBy: column - 1)] - let cost = source == target ? 0 : 1 - - matrix[row][column] = Swift.min( - matrix[row - 1][column] + 1, - matrix[row][column - 1] + 1, - matrix[row - 1][column - 1] + cost - ) + // After trimming common prefix and suffix, self is empty. + guard selfStartTrim < selfEndTrim else { + return target.distance(from: targetStartTrim, + to: targetEndTrim) + } + + // After trimming common prefix and suffix, target is empty. + guard targetStartTrim < targetEndTrim else { + return distance(from: selfStartTrim, + to: selfEndTrim) + } + + let newSelf = self[selfStartTrim.. String { diff --git a/Tests/ArgumentParserUnitTests/StringEditDistanceTests.swift b/Tests/ArgumentParserUnitTests/StringEditDistanceTests.swift index 5967c21e8..ffa7ed94f 100644 --- a/Tests/ArgumentParserUnitTests/StringEditDistanceTests.swift +++ b/Tests/ArgumentParserUnitTests/StringEditDistanceTests.swift @@ -23,5 +23,10 @@ extension StringEditDistanceTests { XCTAssertEqual("bar".editDistance(to: "foo"), 3) XCTAssertEqual("bar".editDistance(to: "baz"), 1) XCTAssertEqual("baz".editDistance(to: "bar"), 1) + XCTAssertEqual("friend".editDistance(to: "fresh"), 3) + XCTAssertEqual("friend".editDistance(to: "friend"), 0) + XCTAssertEqual("friend".editDistance(to: "fried"), 1) + XCTAssertEqual("friend".editDistance(to: "friendly"), 2) + XCTAssertEqual("friendly".editDistance(to: "friend"), 2) } }