MobiusTest/Source/SimpleDiff.swift (65 lines of code) (raw):

// Copyright (c) 2008 - 2013 Paul Butler and contributors // // This sofware may be used under a zlib/libpng-style license: // // This software is provided 'as-is', without any express or implied warranty. In // no event will the authors be held liable for any damages arising from the use // of this software. // // Permission is granted to anyone to use this software for any purpose, including // commercial applications, and to alter it and redistribute it freely, subject to // the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not claim // that you wrote the original software. If you use this software in a product, an // acknowledgment in the product documentation would be appreciated but is not // required. // // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source distribution. enum Difference: Equatable { case insert(ArraySlice<Substring>) case delete(ArraySlice<Substring>) case same(ArraySlice<Substring>) var string: ArraySlice<Substring> { switch self { case .insert(let string): return string case .delete(let string): return string case .same(let string): return string } } var isSame: Bool { switch self { case .same: return true default: return false } } var prefix: String { switch self { case .insert: return "+" case .delete: return "−" case .same: return "\u{2007}" } } } func diff(lhs: ArraySlice<Substring>, rhs: ArraySlice<Substring>) -> [Difference] { var lhsIndexMap = [String: [Int]]() for (index, value) in zip(lhs.indices, lhs) { lhsIndexMap[String(value), default: []].append(index) } var lhsSubStart = lhs.startIndex var rhsSubStart = rhs.startIndex var subLength = 0 var overlap = [Int: Int]() for (indexRhs, value) in zip(rhs.indices, rhs) { var innerOverlap = [Int: Int]() for indexLhs in lhsIndexMap[String(value), default: []] { let innerSubLength = (overlap[indexLhs - 1] ?? 0) + 1 innerOverlap[indexLhs] = innerSubLength if innerSubLength > subLength { subLength = innerSubLength lhsSubStart = indexLhs - subLength + 1 rhsSubStart = indexRhs - subLength + 1 } } overlap = innerOverlap } var diffs = [Difference]() if subLength == 0 { if !lhs.isEmpty { diffs.append(.delete(lhs)) } if !rhs.isEmpty { diffs.append(.insert(rhs)) } } else { diffs.append(contentsOf: diff(lhs: lhs.prefix(upTo: lhsSubStart), rhs: rhs.prefix(upTo: rhsSubStart))) diffs.append(.same(rhs.suffix(from: rhsSubStart).prefix(subLength))) diffs.append(contentsOf: diff(lhs: lhs.suffix(from: lhsSubStart + subLength), rhs: rhs.suffix(from: rhsSubStart + subLength))) } return diffs }