From c0506cbcda082c93f5e6f7da6b8b79e34d26420f Mon Sep 17 00:00:00 2001 From: Tony Arnold Date: Thu, 24 Nov 2016 09:46:17 +1100 Subject: [PATCH] Reformat all Swift source using SwiftFormat https://github.com/nicklockwood/SwiftFormat --- DiffTests/DiffTests.swift | 26 ++-- DiffTests/ExtendedPatchSortTests.swift | 40 +++--- DiffTests/PatchApplyTests.swift | 18 +-- DiffTests/PatchSortTests.swift | 55 ++++---- .../TableViewExample/AppDelegate.swift | 3 - .../MasterViewController.swift | 33 ++--- Graph.playground/Sources/Arrows.swift | 16 +-- .../Sources/CharacterLabels.swift | 10 +- Graph.playground/Sources/GraphView.swift | 43 +++--- Graph.playground/Sources/ViewController.swift | 58 ++++---- Package.swift | 4 +- .../Benchmark/Benchmark/AppDelegate.swift | 4 - .../Benchmark/Benchmark/ViewController.swift | 8 +- PerfTests/PerfTests/main.swift | 1 - PerfTests/Utils/PerformanceTestUtils.swift | 6 +- PerfTests/Utils/Resources/Diff-new.swift | 88 ++++++------ PerfTests/Utils/Resources/Diff-old.swift | 82 +++++------ Sources/Diff+UIKit.swift | 22 +-- Sources/Diff.swift | 130 +++++++++--------- Sources/ExtendedDiff.swift | 51 ++++--- Sources/ExtendedPatch+Apply.swift | 15 +- Sources/ExtendedPatch.swift | 44 +++--- Sources/GenericPatch.swift | 33 ++--- Sources/LinkedList.swift | 12 +- Sources/Patch+Apply.swift | 9 +- Sources/Patch+Sort.swift | 15 +- Sources/Patch.swift | 24 ++-- 27 files changed, 406 insertions(+), 444 deletions(-) diff --git a/DiffTests/DiffTests.swift b/DiffTests/DiffTests.swift index 06627ac..ddad642 100644 --- a/DiffTests/DiffTests.swift +++ b/DiffTests/DiffTests.swift @@ -16,7 +16,7 @@ extension Trace: Hashable { } class DiffTests: XCTestCase { - + let expectations = [ ("kitten", "sitting", "D(0)I(0)D(4)I(4)I(6)"), ("๐Ÿฉitt๐Ÿจng", "kitten", "D(0)I(0)D(4)I(4)D(6)"), @@ -31,9 +31,9 @@ class DiffTests: XCTestCase { ("1234", "1234", ""), ("", "", ""), ("Oh Hi", "Hi Oh", "D(0)D(1)D(2)I(2)I(3)I(4)"), - ("1362", "31526", "D(0)D(2)I(1)I(2)I(4)") + ("1362", "31526", "D(0)D(2)I(1)I(2)I(4)"), ] - + let extendedExpectations = [ ("sitting", "kitten", "D(0)I(0)D(4)I(4)D(6)"), ("๐Ÿฉitt๐Ÿจng", "kitten", "D(0)I(0)D(4)I(4)D(6)"), @@ -51,9 +51,9 @@ class DiffTests: XCTestCase { ("Oh Hi", "Hi Oh", "M(03)M(14)M(22)"), ("Hi Oh", "Oh Hi", "M(03)M(14)M(22)"), ("12345", "12435", "M(23)"), - ("1362", "31526", "M(01)M(24)I(2)") + ("1362", "31526", "M(01)M(24)I(2)"), ] - + func testDiffOutputs() { for expectation in expectations { XCTAssertEqual( @@ -61,7 +61,7 @@ class DiffTests: XCTestCase { expectation.2) } } - + func testExtendedDiffOutputs() { for expectation in extendedExpectations { XCTAssertEqual( @@ -69,15 +69,15 @@ class DiffTests: XCTestCase { expectation.2) } } - + // The tests below check efficiency of the algorithm - + func testDuplicateTraces() { for expectation in expectations { XCTAssertFalse(duplicateTraces(from: expectation.0, to: expectation.1)) } } - + func testTracesOutOfBounds() { for expectation in expectations { if tracesOutOfBounds(from: expectation.0, to: expectation.1) != [] { @@ -85,20 +85,20 @@ class DiffTests: XCTestCase { } } } - + func duplicateTraces(from: String, to: String) -> Bool { let traces = from.characters.diffTraces(to: to.characters) let tracesSet = Set(traces) return !(traces.count == tracesSet.count) } - + func tracesOutOfBounds(from: String, to: String) -> [Trace] { let ac = from.characters let bc = to.characters return ac.diffTraces(to: bc) .filter { $0.to.y > bc.count || $0.to.x > ac.count } } - + func _test( from: String, to: String) -> String { @@ -106,7 +106,7 @@ class DiffTests: XCTestCase { .diff(to: to) .reduce("") { $0 + $1.debugDescription } } - + func _testExtended( from: String, to: String) -> String { diff --git a/DiffTests/ExtendedPatchSortTests.swift b/DiffTests/ExtendedPatchSortTests.swift index b03a599..ca8f2ee 100644 --- a/DiffTests/ExtendedPatchSortTests.swift +++ b/DiffTests/ExtendedPatchSortTests.swift @@ -3,29 +3,29 @@ import XCTest import Diff class ExtendedPatchSortTests: XCTestCase { - + func testDefaultOrder() { let expectations = [ ("gitten", "sitting", "M(0,5)I(0,s)D(4)I(4,i)"), ("Oh Hi", "Hi Oh", "M(0,4)M(0,4)M(0,2)"), ("12345", "12435", "M(2,3)"), ("1362", "31526", "M(0,2)M(1,3)I(2,5)"), - ("221", "122", "M(2,0)") + ("221", "122", "M(2,0)"), ] - + for expectation in expectations { XCTAssertEqual( _extendedTest(from: expectation.0, to: expectation.1), expectation.2) } } - + func testInsertionDeletionMove() { let expectations = [ ("gitten", "sitting", "I(5,i)I(1,s)D(5)M(0,6)"), - ("1362", "31526", "I(3,5)M(0,2)M(1,4)") + ("1362", "31526", "I(3,5)M(0,2)M(1,4)"), ] - + let sort: ExtendedSortingFunction = { fst, snd in switch (fst, snd) { case (.insert, _): @@ -38,7 +38,7 @@ class ExtendedPatchSortTests: XCTestCase { return false } } - + for expectation in expectations { XCTAssertEqual( _extendedTest( @@ -48,13 +48,13 @@ class ExtendedPatchSortTests: XCTestCase { expectation.2) } } - + func testDeletionMoveInsertion() { let expectations = [ ("gitten", "sitting", "D(4)M(0,4)I(0,s)I(4,i)"), - ("1362", "31526", "M(0,2)M(1,3)I(2,5)") + ("1362", "31526", "M(0,2)M(1,3)I(2,5)"), ] - + let sort: ExtendedSortingFunction = { fst, snd in switch (fst, snd) { case (.delete, _): @@ -67,7 +67,7 @@ class ExtendedPatchSortTests: XCTestCase { return false } } - + for expectation in expectations { XCTAssertEqual( _extendedTest( @@ -77,18 +77,18 @@ class ExtendedPatchSortTests: XCTestCase { expectation.2) } } - + func testRandomStringPermutationRandomPatchSort() { - + let sort: ExtendedSortingFunction = { _, _ in arc4random_uniform(2) == 0 } - for _ in 0..<20 { + for _ in 0 ..< 20 { let string1 = "eakjnrsignambmcbdcdhdkmhkolpdgfedcpgabtldjkaqkoobomuhpepirdcrdrgmrmaefesoiildmtnbronpmmbuuplnfnjgdhadkbmprensshiekknhskognpbknpbepmlakducnfktjeookncjpcnpklfedrebstisalskigsuojkookhbmkdafiaftrkrccupgjapqrigbanfbboapmicabeclhentlabourhtqmlboqctgorajirchesaorsgnigattkdrenquffcutffopbjrebegbfmkeikstqsut" let string2 = "mdjqtbchphncsjdkjtutagahmdtfcnjliipmqgrhgajsgotcdgidlghithdgrcmfuausmjnbtjghqblaiuldirulhllidbpcpglfbnfbkbddhdskdplsgjjsusractdplajrctgrcebhesbeneidsititlalsqkhliontgpesglkoorjqeniqaetatamneonhbhunqlfkbmfsjallnejhkcfaeapdnacqdtukcuiheiabqpudmgosssabisrrlmhcmpkgerhesqihdnfjmqgfnmulnfkmpqrsghutfsckurr" let patch = string1.extendedDiff(string2).patch( from: string1.characters, to: string2.characters, - sort:sort) + sort: sort) let result = string1.apply(patch) XCTAssertEqual(result, string2) } @@ -103,13 +103,13 @@ func _extendedTest( sortingFunction: ExtendedSortingFunction? = nil) -> String { guard let sort = sortingFunction else { return extendedPatch( - from: from.characters, - to: to.characters) + from: from.characters, + to: to.characters) .reduce("") { $0 + $1.debugDescription } } return extendedPatch( - from: from.characters, - to: to.characters, - sort: sort) + from: from.characters, + to: to.characters, + sort: sort) .reduce("") { $0 + $1.debugDescription } } diff --git a/DiffTests/PatchApplyTests.swift b/DiffTests/PatchApplyTests.swift index 9cac457..a2b70da 100644 --- a/DiffTests/PatchApplyTests.swift +++ b/DiffTests/PatchApplyTests.swift @@ -3,31 +3,31 @@ import XCTest class PatchApplyTests: XCTestCase { func testString() { - + let testCases: [(String, String, String)] = [ ("", "I(0,A)I(0,B)I(0,C)", "CBA"), ("", "I(0,A)I(1,B)I(1,C)", "ACB"), ("AB", "D(1)I(1,B)I(1,C)", "ACB"), ("AB", "I(1,B)D(0)I(1,C)", "BCB"), - ("A", "I(0,B)D(0)", "A") + ("A", "I(0,B)D(0)", "A"), ] - - testCases.forEach { (seed, patchString, result) in + + testCases.forEach { seed, patchString, result in XCTAssertEqual(seed.apply(stringPatch(from: patchString)), result) } } - + func testCollection() { - + let testCases: [([Int], String, [Int])] = [ ([], "I(0,0)I(0,1)I(0,2)", [2, 1, 0]), ([], "I(0,0)I(1,1)I(1,2)", [0, 2, 1]), ([0, 1], "D(1)I(1,1)I(1,2)", [0, 2, 1]), ([0, 1], "I(1,1)D(0)I(1,2)", [1, 2, 1]), - ([0], "I(0,1)D(0)", [0]) + ([0], "I(0,1)D(0)", [0]), ] - - testCases.forEach { (seed, patchString, result) in + + testCases.forEach { seed, patchString, result in XCTAssertEqual(seed.apply(intPatch(from: patchString)), result) } } diff --git a/DiffTests/PatchSortTests.swift b/DiffTests/PatchSortTests.swift index 194feae..0112e9e 100644 --- a/DiffTests/PatchSortTests.swift +++ b/DiffTests/PatchSortTests.swift @@ -4,7 +4,7 @@ import XCTest class PatchTests: XCTestCase { func testDefaultOrder() { - + let defaultOrder = [ ("kitten", "sitting", "D(0)I(0,s)D(4)I(4,i)I(6,g)"), ("๐Ÿฉitt๐Ÿจng", "kitten", "D(0)I(0,k)D(4)I(4,e)D(6)"), @@ -19,18 +19,18 @@ class PatchTests: XCTestCase { ("", "", ""), ("Oh Hi", "Hi Oh", "D(0)D(0)D(0)I(2, )I(3,O)I(4,h)"), ("1362", "31526", "D(0)D(1)I(1,1)I(2,5)I(4,6)"), - ("1234b2", "ab", "D(0)D(0)D(0)D(0)I(0,a)D(2)") + ("1234b2", "ab", "D(0)D(0)D(0)D(0)I(0,a)D(2)"), ] - + for expectation in defaultOrder { XCTAssertEqual( _test(from: expectation.0, to: expectation.1), expectation.2) } } - + func testInsertionsFirst() { - + let insertionsFirst = [ ("kitten", "sitting", "I(1,s)I(6,i)I(8,g)D(0)D(4)"), ("๐Ÿฉitt๐Ÿจng", "kitten", "I(1,k)I(6,e)D(0)D(4)D(6)"), @@ -44,9 +44,9 @@ class PatchTests: XCTestCase { ("1234", "1234", ""), ("", "", ""), ("Oh Hi", "Hi Oh", "I(5, )I(6,O)I(7,h)D(0)D(0)D(0)"), - ("1362", "31526", "I(3,1)I(4,5)I(6,6)D(0)D(1)") + ("1362", "31526", "I(3,1)I(4,5)I(6,6)D(0)D(1)"), ] - + let insertionsFirstSort = { (element1: Diff.Element, element2: Diff.Element) -> Bool in switch (element1, element2) { case (.insert(let at1), .insert(let at2)): @@ -60,7 +60,7 @@ class PatchTests: XCTestCase { default: fatalError() } } - + for expectation in insertionsFirst { XCTAssertEqual( _test( @@ -70,9 +70,9 @@ class PatchTests: XCTestCase { expectation.2) } } - + func testDeletionsFirst() { - + let deletionsFirst = [ ("kitten", "sitting", "D(0)D(3)I(0,s)I(4,i)I(6,g)"), ("๐Ÿฉitt๐Ÿจng", "kitten", "D(0)D(3)D(4)I(0,k)I(4,e)"), @@ -86,9 +86,9 @@ class PatchTests: XCTestCase { ("1234", "1234", ""), ("", "", ""), ("Oh Hi", "Hi Oh", "D(0)D(0)D(0)I(2, )I(3,O)I(4,h)"), - ("1362", "31526", "D(0)D(1)I(1,1)I(2,5)I(4,6)") + ("1362", "31526", "D(0)D(1)I(1,1)I(2,5)I(4,6)"), ] - + let deletionsFirstSort = { (element1: Diff.Element, element2: Diff.Element) -> Bool in switch (element1, element2) { case (.insert(let at1), .insert(let at2)): @@ -102,7 +102,7 @@ class PatchTests: XCTestCase { default: fatalError() } } - + for expectation in deletionsFirst { XCTAssertEqual( _test( @@ -112,16 +112,16 @@ class PatchTests: XCTestCase { expectation.2) } } - + func testRandomStringPermutationRandomPatchSort() { - + let sort = { (element1: Diff.Element, element2: Diff.Element) -> Bool in return arc4random_uniform(2) == 0 } - for _ in 0..<200 { + for _ in 0 ..< 200 { let randomString = randomAlphaNumericString(length: 30) let permutation = randomAlphaNumericString(length: 30) - let patch = randomString.diff(to: permutation).patch(from: randomString.characters, to: permutation.characters, sort:sort) + let patch = randomString.diff(to: permutation).patch(from: randomString.characters, to: permutation.characters, sort: sort) let result = randomString.apply(patch) XCTAssertEqual(result, permutation) } @@ -129,18 +129,18 @@ class PatchTests: XCTestCase { } func randomAlphaNumericString(length: Int) -> String { - + let allowedChars = "abcdefghijklmnopqrstu" let allowedCharsCount = UInt32(allowedChars.characters.count) var randomString = "" - - for _ in 0.. String { if let sort = sortingFunction { return patch( - from: from.characters, - to: to.characters, - sort: sort) + from: from.characters, + to: to.characters, + sort: sort) .reduce("") { $0 + $1.debugDescription } } return patch( - from: from.characters, - to: to.characters) + from: from.characters, + to: to.characters) .reduce("") { $0 + $1.debugDescription } } - - - diff --git a/Examples/TableViewExample/TableViewExample/AppDelegate.swift b/Examples/TableViewExample/TableViewExample/AppDelegate.swift index d4a62a2..195c1b5 100644 --- a/Examples/TableViewExample/TableViewExample/AppDelegate.swift +++ b/Examples/TableViewExample/TableViewExample/AppDelegate.swift @@ -13,7 +13,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele var window: UIWindow? - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { return true } @@ -39,6 +38,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } - } - diff --git a/Examples/TableViewExample/TableViewExample/MasterViewController.swift b/Examples/TableViewExample/TableViewExample/MasterViewController.swift index 8924c0f..d2f7994 100644 --- a/Examples/TableViewExample/TableViewExample/MasterViewController.swift +++ b/Examples/TableViewExample/TableViewExample/MasterViewController.swift @@ -3,7 +3,7 @@ import UIKit import Diff class MasterViewController: UITableViewController { - + var objects = [ "๐ŸŒž", "๐Ÿฉ", @@ -11,7 +11,7 @@ class MasterViewController: UITableViewController { "๐Ÿฆ„", "๐Ÿ‘‹๐Ÿป", "๐Ÿ™‡๐Ÿฝโ€โ™€๏ธ", - "๐Ÿ”ฅ" + "๐Ÿ”ฅ", ] { didSet { tableView.animateRowChanges( @@ -22,7 +22,6 @@ class MasterViewController: UITableViewController { } } - override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. @@ -30,40 +29,39 @@ class MasterViewController: UITableViewController { let addButton = UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(refresh(_:))) self.navigationItem.rightBarButtonItem = addButton } - + func refresh(_ sender: Any) { - let moveIndices = (0...0).map { _ in (randomIndex(), randomIndex()) } - let insertionIndices = (0...0).map { _ in randomIndex() } - let deleteIndices = (0...0).map { _ in randomIndex() } - - + let moveIndices = (0 ... 0).map { _ in (randomIndex(), randomIndex()) } + let insertionIndices = (0 ... 0).map { _ in randomIndex() } + let deleteIndices = (0 ... 0).map { _ in randomIndex() } + var mutableObjects = objects - - moveIndices.forEach { (from, to) in + + moveIndices.forEach { from, to in let element = mutableObjects.remove(at: from) mutableObjects.insert(element, at: to) } - + insertionIndices.forEach { index in mutableObjects.insert(randomEmoji(), at: index) } - + deleteIndices.forEach { index in mutableObjects.remove(at: index) } - + objects = mutableObjects } - + func randomIndex() -> Int { return Int(arc4random_uniform(UInt32(objects.count))) } func randomEmoji() -> String { - let emojis = (UInt32(0x1F601)...UInt32(0x1F64F)).map { String(UnicodeScalar($0)!) } + let emojis = (UInt32(0x1F601) ... UInt32(0x1F64F)).map { String(UnicodeScalar($0)!) } return emojis[Int(arc4random_uniform(UInt32(emojis.count)))] } - + // MARK: - Table View override func numberOfSections(in tableView: UITableView) -> Int { @@ -80,4 +78,3 @@ class MasterViewController: UITableViewController { return cell } } - diff --git a/Graph.playground/Sources/Arrows.swift b/Graph.playground/Sources/Arrows.swift index 0bb9af9..1319b53 100644 --- a/Graph.playground/Sources/Arrows.swift +++ b/Graph.playground/Sources/Arrows.swift @@ -15,8 +15,8 @@ public extension UIBezierPath { convenience init(arrow: Arrow) { let length = CGFloat(hypotf(Float(arrow.to.x - arrow.from.x), Float(arrow.to.y - arrow.from.y))) var points = [CGPoint]() - - let tailLength = length - arrow.headLength; + + let tailLength = length - arrow.headLength points.append(CGPoint(x: 0, y: arrow.tailWidth / 2)) points.append(CGPoint(x: tailLength, y: arrow.tailWidth / 2)) points.append(CGPoint(x: tailLength, y: arrow.headWidth / 2)) @@ -24,7 +24,7 @@ public extension UIBezierPath { points.append(CGPoint(x: tailLength, y: -arrow.headWidth / 2)) points.append(CGPoint(x: tailLength, y: -arrow.tailWidth / 2)) points.append(CGPoint(x: 0, y: -arrow.tailWidth / 2)) - + let transform = UIBezierPath.transform(from: arrow.from, to: arrow.to, length: length) let path = CGMutablePath() path.addLines(between: points, transform: transform) @@ -33,19 +33,19 @@ public extension UIBezierPath { } private static func transform(from start: CGPoint, to: CGPoint, length: CGFloat) -> CGAffineTransform { - let cosine = (to.x - start.x) / length; - let sine = (to.y - start.y) / length; + let cosine = (to.x - start.x) / length + let sine = (to.y - start.y) / length return CGAffineTransform(a: cosine, b: sine, c: -sine, d: cosine, tx: start.x, ty: start.y) } - + func shapeLayer() -> CAShapeLayer { let l = CAShapeLayer() let bounds = self.bounds let origin = bounds.origin let path = self.copy() as! UIBezierPath - + path.apply(CGAffineTransform(translationX: -origin.x, y: -origin.y)) - + l.path = path.cgPath l.frame = bounds return l diff --git a/Graph.playground/Sources/CharacterLabels.swift b/Graph.playground/Sources/CharacterLabels.swift index 457968c..711443d 100644 --- a/Graph.playground/Sources/CharacterLabels.swift +++ b/Graph.playground/Sources/CharacterLabels.swift @@ -12,14 +12,14 @@ public extension String { func length() -> Int { return lengthOfBytes(using: String.Encoding.utf8) } - + func characterLabels(withFrames frames: [CGRect]) -> [UILabel] { let characters = self.characters - + guard characters.count == frames.count else { return [] } - + let labels = characters.map { $0.label() } let sizes = labels.map { $0.frame.size } let frames = inset(rects: frames, to: sizes) @@ -38,7 +38,7 @@ extension Character { } } -func inset(rects:[CGRect], to: [CGSize]) -> [CGRect] { +func inset(rects: [CGRect], to: [CGSize]) -> [CGRect] { return zip(to, rects).map { size, rect -> CGRect in return rect.inset(to: size) } @@ -46,6 +46,6 @@ func inset(rects:[CGRect], to: [CGSize]) -> [CGRect] { extension CGRect { func inset(to size: CGSize) -> CGRect { - return self.insetBy(dx: (self.width-size.width)/2, dy: (self.height-size.height)/2).standardized + return self.insetBy(dx: (self.width - size.width) / 2, dy: (self.height - size.height) / 2).standardized } } diff --git a/Graph.playground/Sources/GraphView.swift b/Graph.playground/Sources/GraphView.swift index f18a246..f9c12d1 100644 --- a/Graph.playground/Sources/GraphView.swift +++ b/Graph.playground/Sources/GraphView.swift @@ -23,58 +23,57 @@ public extension Graph { guard grid.x > 0 && grid.y > 0 else { return [] } - + var layers = [CALayer]() let lineWidth: CGFloat = 1 let layer: (CGRect) -> CALayer = { rect in - let layer = CALayer(); + let layer = CALayer() layer.frame = rect layer.backgroundColor = UIColor.white.cgColor return layer } - - for i in 0...(grid.x) { - let rect = CGRect(x: x(at:i), y: y(at:0), width: lineWidth, height: bounds.height) + + for i in 0 ... (grid.x) { + let rect = CGRect(x: x(at: i), y: y(at: 0), width: lineWidth, height: bounds.height) layers.append(layer(rect)) } - - for i in 0...(grid.y) { - let rect = CGRect(x: x(at:0), y: y(at:i), width: bounds.width, height: lineWidth) + + for i in 0 ... (grid.y) { + let rect = CGRect(x: x(at: 0), y: y(at: i), width: bounds.width, height: lineWidth) layers.append(layer(rect)) } - + return layers } - + func rect(at point: Point) -> CGRect { let origin = coordinates(at: point) - let width = x(at: point.x+1) - origin.x - let height = y(at: point.y+1) - origin.y + let width = x(at: point.x + 1) - origin.x + let height = y(at: point.y + 1) - origin.y return CGRect(origin: origin, size: CGSize(width: width, height: height)) } - + func rects(row y: Int) -> [CGRect] { - return (0.. [CGRect] { - return (0.. CGPoint { return CGPoint(x: x(at: point.x), y: y(at: point.y)) } - + func x(at x: Int) -> CGFloat { - return bounds.width/CGFloat(grid.x)*CGFloat(x) + bounds.origin.x + return bounds.width / CGFloat(grid.x) * CGFloat(x) + bounds.origin.x } - + func y(at y: Int) -> CGFloat { - return bounds.height/CGFloat(grid.y)*CGFloat(y) + bounds.origin.y + return bounds.height / CGFloat(grid.y) * CGFloat(y) + bounds.origin.y } } - diff --git a/Graph.playground/Sources/ViewController.swift b/Graph.playground/Sources/ViewController.swift index 2975c42..eaf56ec 100644 --- a/Graph.playground/Sources/ViewController.swift +++ b/Graph.playground/Sources/ViewController.swift @@ -16,11 +16,13 @@ public class GraphViewController: UIViewController { view.backgroundColor = self.backgroundColor() return view }() + lazy var slider: UISlider = { let slider = UISlider() slider.addTarget(self, action: #selector(sliderDidChange), for: .valueChanged) return slider }() + lazy var stackView: UIStackView = { let stackView = UIStackView(arrangedSubviews: [self.graphView, self.dValueLabel, self.kValueLabel, self.slider]) stackView.frame = self.view.bounds @@ -36,66 +38,69 @@ public class GraphViewController: UIViewController { view.setNeedsLayout() } } + var graph: Graph { let grid = Grid(x: diffStrings.0.length(), y: diffStrings.1.length()) return Graph(grid: grid, bounds: graphView.frame.insetBy(dx: 50, dy: 50)) } + var arrows: [CAShapeLayer] = [] { didSet { - oldValue.forEach {$0.removeFromSuperlayer()} - arrows.forEach {graphView.layer.addSublayer($0)} + oldValue.forEach { $0.removeFromSuperlayer() } + arrows.forEach { graphView.layer.addSublayer($0) } } } + var traces: [Trace] { return Array(diffStrings.0.characters).diffTraces(Array(diffStrings.1.characters)) } - + func display(range: Range? = nil) { graphView.layer.sublayers?.forEach { $0.removeFromSuperlayer() } var displayedTraces = traces if let range = range { displayedTraces = Array(displayedTraces[range]) - slider.value = Float(range.upperBound-1)/Float(traces.count-1) + slider.value = Float(range.upperBound - 1) / Float(traces.count - 1) } else { slider.value = 1 } - + graph.gridLayers().forEach { graphView.layer.addSublayer($0) } arrows = displayedTraces.map { $0.shapeLayer(on: graph) } - + let labels1 = diffStrings.0.characterLabels(withFrames: graph.rects(row: -1)) let labels2 = diffStrings.1.characterLabels(withFrames: graph.rects(column: -1)) (labels1 + labels2).forEach { graphView.addSubview($0) } - - if let maxElement = displayedTraces.max(by: {$0.D < $1.D}) { + + if let maxElement = displayedTraces.max(by: { $0.D < $1.D }) { dValueLabel.text = "Number of differences: \(maxElement.D)" kValueLabel.text = "k value \(maxElement.k())" } } - + func sliderDidChange(sender: UISlider) { - let maxIndex = Int(sender.value * Float(traces.count-1)) - let range: Range = 0.. = 0 ..< maxIndex + 1 display(range: range) } - + public override func viewDidLoad() { view.backgroundColor = backgroundColor() view.addSubview(stackView) } - + public override func viewDidLayoutSubviews() { display() } func backgroundColor() -> UIColor { - return UIColor(red: 65/255, green: 153/255, blue: 1, alpha: 1) + return UIColor(red: 65 / 255, green: 153 / 255, blue: 1, alpha: 1) } - - required public init?(coder aDecoder: NSCoder) { + + public required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + public init(string1: String, string2: String) { super.init(nibName: nil, bundle: nil) diffStrings = (string1, string2) @@ -106,26 +111,26 @@ extension Trace { func arrow(on graph: Graph) -> Arrow { let from = graph.coordinates(at: self.from) let to = graph.coordinates(at: self.to) - + let translatedCoordinates: (from: CGPoint, to: CGPoint) = { - let yDelta = (to.y-from.y)/20 - let xDelta = (to.x-from.x)/20 - + let yDelta = (to.y - from.y) / 20 + let xDelta = (to.x - from.x) / 20 + switch type() { case .deletion: - return (CGPoint(x: from.x+xDelta, y: from.y), CGPoint(x: to.x-xDelta, y: to.y)) + return (CGPoint(x: from.x + xDelta, y: from.y), CGPoint(x: to.x - xDelta, y: to.y)) case .insertion: - return (CGPoint(x: from.x, y: from.y+yDelta), CGPoint(x: to.x, y: to.y-yDelta)) + return (CGPoint(x: from.x, y: from.y + yDelta), CGPoint(x: to.x, y: to.y - yDelta)) case .matchPoint: - return (CGPoint(x: from.x+xDelta, y: from.y+yDelta), CGPoint(x: to.x-xDelta, y: to.y-yDelta)) + return (CGPoint(x: from.x + xDelta, y: from.y + yDelta), CGPoint(x: to.x - xDelta, y: to.y - yDelta)) } }() return Arrow(from: translatedCoordinates.from, to: translatedCoordinates.to, tailWidth: 6, headWidth: 12, headLength: 10) } - + func shapeLayer(on graph: Graph) -> CAShapeLayer { let arrowLayer = UIBezierPath(arrow: arrow(on: graph)).shapeLayer() - + switch type() { case .deletion: arrowLayer.fillColor = UIColor.red.cgColor @@ -135,6 +140,5 @@ extension Trace { arrowLayer.fillColor = UIColor.white.cgColor } return arrowLayer - } } diff --git a/Package.swift b/Package.swift index fee10d1..f46c1f0 100644 --- a/Package.swift +++ b/Package.swift @@ -3,6 +3,6 @@ import PackageDescription let package = Package( name: "Diff", dependencies: [] - ) +) -products.append(Product(name:"Diff", type: .Library(.Dynamic), modules: ["Diff"])) +products.append(Product(name: "Diff", type: .Library(.Dynamic), modules: ["Diff"])) diff --git a/PerfTests/Benchmark/Benchmark/AppDelegate.swift b/PerfTests/Benchmark/Benchmark/AppDelegate.swift index e0d3058..4a964be 100644 --- a/PerfTests/Benchmark/Benchmark/AppDelegate.swift +++ b/PerfTests/Benchmark/Benchmark/AppDelegate.swift @@ -13,7 +13,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. return true @@ -40,7 +39,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } - - } - diff --git a/PerfTests/Benchmark/Benchmark/ViewController.swift b/PerfTests/Benchmark/Benchmark/ViewController.swift index d9d75f8..b94a613 100644 --- a/PerfTests/Benchmark/Benchmark/ViewController.swift +++ b/PerfTests/Benchmark/Benchmark/ViewController.swift @@ -12,13 +12,13 @@ class ViewController: UIViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - + let from = pathForFile(fileName: "Diff-old") let to = pathForFile(fileName: "Diff-new") - + let dwifft = performDiff(from, toFilePath: to, diffFunc: performDwifft) let diff = performDiff(from, toFilePath: to, diffFunc: diffSwift) - + print("Both libraries are compiled with -O -whole-module-optimization\n") print(" | Diff.swift | Dwifft ") print("---------|------------|--------") @@ -29,6 +29,6 @@ class ViewController: UIViewController { } } -func pathForFile(fileName: String) -> String{ +func pathForFile(fileName: String) -> String { return Bundle.main.path(forResource: fileName, ofType: "swift")! } diff --git a/PerfTests/PerfTests/main.swift b/PerfTests/PerfTests/main.swift index 396472f..39088ff 100644 --- a/PerfTests/PerfTests/main.swift +++ b/PerfTests/PerfTests/main.swift @@ -21,4 +21,3 @@ print(" same | \(diff.same) |") print(" created | \(diff.created) |") print(" deleted | \(diff.deleted) |") print(" diff | \(diff.changed) |") - diff --git a/PerfTests/Utils/PerformanceTestUtils.swift b/PerfTests/Utils/PerformanceTestUtils.swift index 64d30f9..b5ebb58 100644 --- a/PerfTests/Utils/PerformanceTestUtils.swift +++ b/PerfTests/Utils/PerformanceTestUtils.swift @@ -14,13 +14,13 @@ func performDiff(_ fromFilePath: String, toFilePath: String, repeatCount: Int = let new = file(toFilePath) let compare: ([Character], [Character]) -> String = { a, b in var time: CFTimeInterval = 0 - for _ in 0.. Void) -> CFTimeInterval { func file(_ path: String) -> [Character] { return try! Array(String(contentsOfFile: path).characters) - } func diffSwift(_ a: [Character], b: [Character]) { @@ -68,4 +67,3 @@ func launchPath() -> String { func proccessPath() -> String { return currentDirectoryPath().appending(launchPath()) } - diff --git a/PerfTests/Utils/Resources/Diff-new.swift b/PerfTests/Utils/Resources/Diff-new.swift index 48f90c2..af3c8ee 100644 --- a/PerfTests/Utils/Resources/Diff-new.swift +++ b/PerfTests/Utils/Resources/Diff-new.swift @@ -12,7 +12,7 @@ public enum DiffElement { extension DiffElement { init(trace: Trace) { - if trace.from.x+1 == trace.to.x && trace.from.y+1 == trace.to.y { + if trace.from.x + 1 == trace.to.x && trace.from.y + 1 == trace.to.y { self = .Equal(aIndex: trace.to.x, bIndex: trace.to.y) } else if trace.from.y < trace.to.y { self = .Insert(from: trace.from.y, at: trace.from.x) @@ -41,7 +41,7 @@ enum TraceType { extension Trace { func type() -> TraceType { - if from.x+1 == to.x && from.y+1 == to.y { + if from.x + 1 == to.x && from.y + 1 == to.y { return .MatchPoint } else if from.y < to.y { return .Insertion @@ -49,13 +49,13 @@ extension Trace { return .Deletion } } - + func k() -> Int { return from.x - from.y } } -public extension RangeReplaceableCollectionType where Self.Generator.Element : Equatable, Self.Index : SignedIntegerType { +public extension RangeReplaceableCollectionType where Self.Generator.Element: Equatable, Self.Index: SignedIntegerType { public func apply(patch: Patch) -> Self { var mutableSelf = self @@ -84,63 +84,61 @@ public extension RangeReplaceableCollectionType where Self.Generator.Element : E } } -public extension CollectionType where Generator.Element : Equatable, Index : SignedIntegerType { - - public func diffTraces(b: Self) -> Array - { - +public extension CollectionType where Generator.Element: Equatable, Index: SignedIntegerType { + + public func diffTraces(b: Self) -> Array { + let N = Int(self.count.toIntMax()) let M = Int(b.count.toIntMax()) var traces = Array() - - let max = N+M // this is arbitrary, maximum difference between a and b. N+M assures that this algorithm always finds a diff - + + let max = N + M // this is arbitrary, maximum difference between a and b. N+M assures that this algorithm always finds a diff + var V = Array(count: 2 * Int(max) + 1, repeatedValue: -1) // from [0...2*max], it is -max...max in the whitepaper - - V[max+1] = 0 - - for D in 0...max { + + V[max + 1] = 0 + + for D in 0 ... max { for k in (-D).stride(through: D, by: 2) { - - let index = k+max - + + let index = k + max + // if x value for bigger (x-y) V[index-1] is smaller than x value for smaller (x-y) V[index+1] // then return smaller (x-y) // well, why?? // It means that y = x - k will be bigger // otherwise y = x - k will be smaller // What is the conclusion? Hell knows. - - + /* - case 1: k == -D: take the furthest going k+1 trace and go greedly down. We take x of the furthest going k+1 path and go greedly down. - case 2: k == D: take the furthest going k-d trace and go right. Again, k+1 is unknown so we have to take k-1. What's more k-1 is right most one trace. We add 1 so that we go 1 to the right direction and stay on the same y - case 3: -D Int in - if k == -D || (k != D && V[index-1] < V[index+1]) { //V[index-1] - y is bigger V[index+1] - y is smaller - let x = V[index+1] - traces.append(Trace(from: Point(x: x, y: x-k-1), to: Point(x: x, y: x-k), D: D)) + if k == -D || (k != D && V[index - 1] < V[index + 1]) { // V[index-1] - y is bigger V[index+1] - y is smaller + let x = V[index + 1] + traces.append(Trace(from: Point(x: x, y: x - k - 1), to: Point(x: x, y: x - k), D: D)) return x // go down AKA insert } else { - let x = V[index-1]+1 - traces.append(Trace(from: Point(x: x-1, y: x-k), to: Point(x: x, y: x-k), D: D)) + let x = V[index - 1] + 1 + traces.append(Trace(from: Point(x: x - 1, y: x - k), to: Point(x: x, y: x - k), D: D)) return x // go right AKA delete } }() - + var y = x - k - + // keep going as long as they match on diagonal k while x < N && y < M && self[Self.Index(x.toIntMax())] == b[Self.Index(y.toIntMax())] { x += 1 y += 1 - traces.append(Trace(from: Point(x: x-1, y: y-1), to: Point(x: x, y: y), D: D)) + traces.append(Trace(from: Point(x: x - 1, y: y - 1), to: Point(x: x, y: y), D: D)) } - + V[index] = x - + if x >= N && y >= M { return traces } @@ -148,35 +146,31 @@ public extension CollectionType where Generator.Element : Equatable, Index : Sig } return [] } - + func diff(b: Self) -> Diff { return findPath(diffTraces(b), n: Int(self.count.toIntMax()), m: Int(b.count.toIntMax())) } - + private func findPath(traces: Array, n: Int, m: Int) -> Diff { - + guard traces.count > 0 else { return Diff(elements: []) } - + var array = Array() var item = traces.last! array.append(item) - + for trace in traces.reverse() { if trace.to.x == item.from.x && trace.to.y == item.from.y { array.append(trace) item = trace } - + if trace.from.x == 0 && trace.from.y == 0 { - break; + break } } return Diff(elements: array.reverse().map { DiffElement(trace: $0) }) } - } - - - diff --git a/PerfTests/Utils/Resources/Diff-old.swift b/PerfTests/Utils/Resources/Diff-old.swift index 9feac88..5446d17 100644 --- a/PerfTests/Utils/Resources/Diff-old.swift +++ b/PerfTests/Utils/Resources/Diff-old.swift @@ -12,7 +12,7 @@ public enum DiffElement { extension DiffElement { init(trace: Trace) { - if trace.from.x+1 == trace.to.x && trace.from.y+1 == trace.to.y { + if trace.from.x + 1 == trace.to.x && trace.from.y + 1 == trace.to.y { self = .Equal(aIndex: trace.to.x, bIndex: trace.to.y) } else if trace.from.y < trace.to.y { self = .Insert(from: trace.from.y, at: trace.from.x) @@ -32,7 +32,7 @@ struct Trace { let to: Point } -public extension RangeReplaceableCollectionType where Self.Generator.Element : Equatable, Self.Index : SignedIntegerType { +public extension RangeReplaceableCollectionType where Self.Generator.Element: Equatable, Self.Index: SignedIntegerType { public func apply(patch: Patch) -> Self { var mutableSelf = self @@ -61,63 +61,61 @@ public extension RangeReplaceableCollectionType where Self.Generator.Element : E } } -public extension CollectionType where Generator.Element : Equatable, Index : SignedIntegerType { - - public func diff(b: Self) -> Diff - { - +public extension CollectionType where Generator.Element: Equatable, Index: SignedIntegerType { + + public func diff(b: Self) -> Diff { + let N = Int(self.count.toIntMax()) let M = Int(b.count.toIntMax()) var traces = Array() - - let max = N+M // this is arbitrary, maximum difference between a and b. N+M assures that this algorithm always finds a diff - + + let max = N + M // this is arbitrary, maximum difference between a and b. N+M assures that this algorithm always finds a diff + var V = Array(count: 2 * Int(max) + 1, repeatedValue: -1) // from [0...2*max], it is -max...max in the whitepaper - - V[max+1] = 0 - - for D in 0...max { + + V[max + 1] = 0 + + for D in 0 ... max { for k in (-D).stride(through: D, by: 2) { - - let index = k+max - + + let index = k + max + // if x value for bigger (x-y) V[index-1] is smaller than x value for smaller (x-y) V[index+1] // then return smaller (x-y) // well, why?? // It means that y = x - k will be bigger // otherwise y = x - k will be smaller // What is the conclusion? Hell knows. - - + /* - case 1: k == -D: take the furthest going k+1 trace and go greedly down. We take x of the furthest going k+1 path and go greedly down. - case 2: k == D: take the furthest going k-d trace and go right. Again, k+1 is unknown so we have to take k-1. What's more k-1 is right most one trace. We add 1 so that we go 1 to the right direction and stay on the same y - case 3: -D Int in - if k == -D || (k != D && V[index-1] < V[index+1]) { //V[index-1] - y is bigger V[index+1] - y is smaller - let x = V[index+1] - traces.append(Trace(from: Point(x: x, y: x-k-1), to: Point(x: x, y: x-k))) + if k == -D || (k != D && V[index - 1] < V[index + 1]) { // V[index-1] - y is bigger V[index+1] - y is smaller + let x = V[index + 1] + traces.append(Trace(from: Point(x: x, y: x - k - 1), to: Point(x: x, y: x - k))) return x // go down AKA insert } else { - let x = V[index-1]+1 - traces.append(Trace(from: Point(x: x-1, y: x-k), to: Point(x: x, y: x-k))) + let x = V[index - 1] + 1 + traces.append(Trace(from: Point(x: x - 1, y: x - k), to: Point(x: x, y: x - k))) return x // go right AKA delete } }() - + var y = x - k - + // keep going as long as they match on diagonal k while x < N && y < M && self[Self.Index(x.toIntMax())] == b[Self.Index(y.toIntMax())] { x += 1 y += 1 - traces.append(Trace(from: Point(x: x-1, y: y-1), to: Point(x: x, y: y))) + traces.append(Trace(from: Point(x: x - 1, y: y - 1), to: Point(x: x, y: y))) } - + V[index] = x - + if x >= N && y >= M { return findPath(traces, n: N, m: M) } @@ -125,31 +123,27 @@ public extension CollectionType where Generator.Element : Equatable, Index : Sig } return Diff(elements: []) } - + private func findPath(traces: Array, n: Int, m: Int) -> Diff { - + guard traces.count > 0 else { return Diff(elements: []) } - + var array = Array() var item = traces.last! array.append(item) - + for trace in traces.reverse() { if trace.to.x == item.from.x && trace.to.y == item.from.y { array.append(trace) item = trace } - + if trace.from.x == 0 && trace.from.y == 0 { - break; + break } } return Diff(elements: array.reverse().map { DiffElement(trace: $0) }) } - } - - - diff --git a/Sources/Diff+UIKit.swift b/Sources/Diff+UIKit.swift index 758c870..e9e7e44 100644 --- a/Sources/Diff+UIKit.swift +++ b/Sources/Diff+UIKit.swift @@ -5,7 +5,7 @@ struct BatchUpdate { let deletions: [IndexPath] let insertions: [IndexPath] let moves: [(from: IndexPath, to: IndexPath)] - + init(diff: ExtendedDiff) { deletions = diff.flatMap { element -> IndexPath? in switch element { @@ -32,7 +32,7 @@ struct BatchUpdate { } public extension UITableView { - + /// Animates rows which changed between oldData and newData. /// /// - parameter oldData: Data which reflects the previous state of UITableView @@ -44,7 +44,7 @@ public extension UITableView { newData: T, deletionAnimation: UITableViewRowAnimation = .automatic, insertionAnimation: UITableViewRowAnimation = .automatic - ) where T.Iterator.Element: Equatable { + ) where T.Iterator.Element: Equatable { let update = BatchUpdate(diff: oldData.extendedDiff(newData)) beginUpdates() deleteRows(at: update.deletions, with: deletionAnimation) @@ -55,7 +55,7 @@ public extension UITableView { } public extension UICollectionView { - + /// Animates items which changed between oldData and newData. /// /// - parameter oldData: Data which reflects the previous state of UITableView @@ -63,12 +63,12 @@ public extension UICollectionView { public func animateItemChanges( oldData: T, newData: T - ) where T.Iterator.Element: Equatable { - performBatchUpdates({ - let update = BatchUpdate(diff: oldData.extendedDiff(newData)) - self.deleteItems(at: update.deletions) - self.insertItems(at: update.insertions) - update.moves.forEach { self.moveItem(at: $0.from, to: $0.to) } - }, completion: nil) + ) where T.Iterator.Element: Equatable { + performBatchUpdates({ + let update = BatchUpdate(diff: oldData.extendedDiff(newData)) + self.deleteItems(at: update.deletions) + self.insertItems(at: update.insertions) + update.moves.forEach { self.moveItem(at: $0.from, to: $0.to) } + }, completion: nil) } } diff --git a/Sources/Diff.swift b/Sources/Diff.swift index 628889a..09911ce 100644 --- a/Sources/Diff.swift +++ b/Sources/Diff.swift @@ -1,9 +1,9 @@ public protocol DiffProtocol: Collection, Sequence { - + associatedtype DiffElementType associatedtype Index = Array.Index - + var elements: [DiffElementType] { get } } @@ -12,16 +12,16 @@ public protocol DiffProtocol: Collection, Sequence { Examples: "12" -> "": D(0)D(1) "" -> "12": I(0)I(1) - + SeeAlso: Diff */ public struct Diff: DiffProtocol { - + public enum Element { case insert(at: Int) case delete(at: Int) } - + /// Returns the position immediately after the given index. /// /// - Parameter i: A valid index of the collection. `i` must be less than @@ -30,7 +30,7 @@ public struct Diff: DiffProtocol { public func index(after i: Int) -> Int { return i + 1 } - + /// An array of particular diff operations public let elements: [Diff.Element] } @@ -46,7 +46,7 @@ extension Diff.Element { return nil } } - + func at() -> Int { switch self { case let .delete(at): @@ -68,7 +68,6 @@ public func ==(l: Point, r: Point) -> Bool { return (l.x == r.x) && (l.y == r.y) } - /// A data structure representing single trace produced by the diff algorithm. See the [paper](http://www.xmailserver.org/diff2.pdf) for more information on traces. public struct Trace { public let from: Point @@ -77,7 +76,7 @@ public struct Trace { } extension Trace: Equatable { - static public func ==(l: Trace, r: Trace) -> Bool { + public static func ==(l: Trace, r: Trace) -> Bool { return (l.from == r.from) && (l.to == r.to) } } @@ -90,7 +89,7 @@ enum TraceType { extension Trace { func type() -> TraceType { - if from.x+1 == to.x && from.y+1 == to.y { + if from.x + 1 == to.x && from.y + 1 == to.y { return .matchPoint } else if from.y < to.y { return .insertion @@ -98,15 +97,14 @@ extension Trace { return .deletion } } - + func k() -> Int { return from.x - from.y } } public extension String { - - + /// Creates a diff between the calee and `to` string /// /// - parameter to: a string to compare the calee to. @@ -118,8 +116,7 @@ public extension String { } return characters.diff(to.characters) } - - + /// Creates an extended diff (includes insertions, deletions, and moves) between the calee and `other` string /// /// - parameter other: a string to compare the calee to. @@ -141,7 +138,7 @@ public extension String { extension Array { func value(at index: Index) -> Iterator.Element? { - if (index < 0 || index >= self.count) { + if index < 0 || index >= self.count { return nil } return self[index] @@ -155,9 +152,8 @@ struct TraceStep { let nextX: Int? } -public extension Collection where Iterator.Element : Equatable { - - +public extension Collection where Iterator.Element: Equatable { + /// Creates a diff between the calee and `other` collection /// /// - parameter other: a collection to compare the calee to @@ -166,66 +162,65 @@ public extension Collection where Iterator.Element : Equatable { public func diff(_ other: Self) -> Diff { return findPath(diffTraces(to: other), n: Int(self.count.toIntMax()), m: Int(other.count.toIntMax())) } - - + /// Generates all traces required to create an output diff. See the [paper](http://www.xmailserver.org/diff2.pdf) for more information on traces. /// /// - parameter to: other collection /// /// - returns: all traces required to create an output diff public func diffTraces(to: Self) -> [Trace] { - if (self.count == 0 && to.count == 0) { + if self.count == 0 && to.count == 0 { return [] - } else if (self.count == 0) { + } else if self.count == 0 { return tracesForInsertions(to: to) - } else if (to.count == 0) { + } else if to.count == 0 { return tracesForDeletions() } else { return myersDiffTraces(to: to) } } - + fileprivate func tracesForDeletions() -> [Trace] { var traces = [Trace]() - for index in 0.. [Trace] { var traces = [Trace]() - for index in 0.. [Trace] { - + let fromCount = Int(self.count.toIntMax()) let toCount = Int(to.count.toIntMax()) var traces = Array() - - let max = fromCount+toCount // this is arbitrary, maximum difference between from and to. N+M assures that this algorithm always finds from diff - + + let max = fromCount + toCount // this is arbitrary, maximum difference between from and to. N+M assures that this algorithm always finds from diff + var vertices = Array(repeating: -1, count: 2 * Int(max) + 1) // from [0...2*max], it is -max...max in the whitepaper - - vertices[max+1] = 0 - - for numberOfDifferences in 0...max { + + vertices[max + 1] = 0 + + for numberOfDifferences in 0 ... max { for k in stride(from: (-numberOfDifferences), through: numberOfDifferences, by: 2) { - - let index = k+max - let traceStep = TraceStep(D: numberOfDifferences, k: k, previousX: vertices.value(at: index-1), nextX: vertices.value(at: index+1)) + + let index = k + max + let traceStep = TraceStep(D: numberOfDifferences, k: k, previousX: vertices.value(at: index - 1), nextX: vertices.value(at: index + 1)) if let trace = bound(trace: nextTrace(traceStep), maxX: fromCount, maxY: toCount) { var x = trace.to.x var y = trace.to.y - + traces.append(trace) - + // keep going as long as they match on diagonal k while x >= 0 && y >= 0 && x < fromCount && y < toCount { let targetItem = to.itemOnStartIndex(advancedBy: y) @@ -233,14 +228,14 @@ public extension Collection where Iterator.Element : Equatable { if baseItem == targetItem { x += 1 y += 1 - traces.append(Trace(from: Point(x: x-1, y: y-1), to: Point(x: x, y: y), D: numberOfDifferences)) + traces.append(Trace(from: Point(x: x - 1, y: y - 1), to: Point(x: x, y: y), D: numberOfDifferences)) } else { break } } - + vertices[index] = x - + if x >= fromCount && y >= toCount { return traces } @@ -249,38 +244,38 @@ public extension Collection where Iterator.Element : Equatable { } return [] } - + fileprivate func bound(trace: Trace, maxX: Int, maxY: Int) -> Trace? { guard trace.to.x <= maxX && trace.to.y <= maxY else { return nil } return trace } - + fileprivate func nextTrace(_ traceStep: TraceStep) -> Trace { let traceType = nextTraceType(traceStep) let k = traceStep.k let D = traceStep.D - - if traceType == .insertion { + + if traceType == .insertion { let x = traceStep.nextX! - return Trace(from: Point(x: x, y: x-k-1), to: Point(x: x, y: x-k), D: D) + return Trace(from: Point(x: x, y: x - k - 1), to: Point(x: x, y: x - k), D: D) } else { let x = traceStep.previousX! + 1 - return Trace(from: Point(x: x-1, y: x-k), to: Point(x: x, y: x-k), D: D) + return Trace(from: Point(x: x - 1, y: x - k), to: Point(x: x, y: x - k), D: D) } } - + fileprivate func nextTraceType(_ traceStep: TraceStep) -> TraceType { let D = traceStep.D let k = traceStep.k let previousX = traceStep.previousX let nextX = traceStep.nextX - + if k == -D { return .insertion } else if k != D { - if let previousX = previousX, let nextX = nextX , previousX < nextX { + if let previousX = previousX, let nextX = nextX, previousX < nextX { return .insertion } return .deletion @@ -288,28 +283,28 @@ public extension Collection where Iterator.Element : Equatable { return .deletion } } - + fileprivate func findPath(_ traces: [Trace], n: Int, m: Int) -> Diff { - + guard traces.count > 0 else { return Diff(elements: []) } - + var array = [Trace]() var item = traces.last! array.append(item) - + for trace in traces.reversed() { if trace.to.x == item.from.x && trace.to.y == item.from.y { array.insert(trace, at: 0) item = trace - + if trace.from == Point(x: 0, y: 0) { - break; + break } } } - + return Diff(elements: array .flatMap { Diff.Element(trace: $0) } ) @@ -317,17 +312,17 @@ public extension Collection where Iterator.Element : Equatable { } extension DiffProtocol { - + public typealias IndexType = Array.Index - + public var startIndex: IndexType { return elements.startIndex } - + public var endIndex: IndexType { return elements.endIndex } - + public subscript(i: IndexType) -> DiffElementType { return elements[i] } @@ -343,4 +338,3 @@ extension Diff.Element: CustomDebugStringConvertible { } } } - diff --git a/Sources/ExtendedDiff.swift b/Sources/ExtendedDiff.swift index 7d83617..37b0999 100644 --- a/Sources/ExtendedDiff.swift +++ b/Sources/ExtendedDiff.swift @@ -1,20 +1,20 @@ /** - A sequence of deletions, insertions, and moves where deletions point to locations in the source and insertions point to locations in the output. - Examples: - "12" -> "": D(0)D(1) - "" -> "12": I(0)I(1) - - SeeAlso: Diff -*/ + A sequence of deletions, insertions, and moves where deletions point to locations in the source and insertions point to locations in the output. + Examples: + "12" -> "": D(0)D(1) + "" -> "12": I(0)I(1) + + SeeAlso: Diff + */ public struct ExtendedDiff: DiffProtocol { - + public enum Element { case insert(at: Int) case delete(at: Int) case move(from: Int, to: Int) } - + /// Returns the position immediately after the given index. /// /// - Parameter i: A valid index of the collection. `i` must be less than @@ -23,16 +23,14 @@ public struct ExtendedDiff: DiffProtocol { public func index(after i: Int) -> Int { return i + 1 } - - + /// Diff used to compute an instance public let source: Diff /// An array which holds indices of diff elements in the source diff (i.e. diff without moves). let sourceIndex: [Int] /// An array which holds indices of diff elements in a diff where move's subelements (deletion and insertion) are ordered accordingly let reorderedIndex: [Int] - - + /// An array of particular diff operations public let elements: [ExtendedDiff.Element] let moveIndices: Set @@ -49,9 +47,8 @@ extension ExtendedDiff.Element { } } -public extension Collection where Iterator.Element : Equatable { +public extension Collection where Iterator.Element: Equatable { - /// Creates an extended diff between the calee and `other` collection /// /// - parameter other: a collection to compare the calee to @@ -60,19 +57,17 @@ public extension Collection where Iterator.Element : Equatable { public func extendedDiff(_ other: Self) -> ExtendedDiff { return extendedDiffFrom(diff(other), other: other) } - + private func extendedDiffFrom(_ diff: Diff, other: Self) -> ExtendedDiff { - - + var elements: [ExtendedDiff.Element] = [] var moveOriginIndices = Set() var moveTargetIndices = Set() // It maps indices after reordering (e.g. bringing move origin and target next to each other in the output) to their positions in the source Diff var sourceIndex = [Int]() - - + // Complexity O(d^2) where d is the length of the diff - + /* * 1. Iterate all objects * 2. For every iteration find the next matching element @@ -85,7 +80,7 @@ public extension Collection where Iterator.Element : Equatable { * 4. Remove the candidate and match and insert the move in the place of the candidate * */ - + for candidateIndex in diff.indices { if !moveTargetIndices.contains(candidateIndex) && !moveOriginIndices.contains(candidateIndex) { let candidate = diff[candidateIndex] @@ -113,9 +108,9 @@ public extension Collection where Iterator.Element : Equatable { } } } - + let reorderedIndices = flip(array: sourceIndex) - + return ExtendedDiff( source: diff, sourceIndex: sourceIndex, @@ -124,14 +119,14 @@ public extension Collection where Iterator.Element : Equatable { moveIndices: moveOriginIndices ) } - + func firstMatch( _ diff: Diff, dirtyIndices: Set, candidate: Diff.Element, candidateIndex: Diff.Index, other: Self) -> (ExtendedDiff.Element, Diff.Index)? { - for matchIndex in (candidateIndex + 1).. ExtendedDiff.Element? { switch (candidate, match) { case (.delete, .insert): @@ -156,7 +151,7 @@ public extension Collection where Iterator.Element : Equatable { } return nil } - + func itemOnStartIndex(advancedBy n: Int) -> Iterator.Element { return self[self.index(startIndex, offsetBy: IndexDistance(n.toIntMax()))] } diff --git a/Sources/ExtendedPatch+Apply.swift b/Sources/ExtendedPatch+Apply.swift index 171750d..ed415cc 100644 --- a/Sources/ExtendedPatch+Apply.swift +++ b/Sources/ExtendedPatch+Apply.swift @@ -1,11 +1,11 @@ // TODO: Fix ugly copy paste :/ -public extension RangeReplaceableCollection where Self.Iterator.Element : Equatable { - +public extension RangeReplaceableCollection where Self.Iterator.Element: Equatable { + public func apply(_ patch: [ExtendedPatch]) -> Self { var mutableSelf = self - + for change in patch { switch change { case let .insertion(i, element): @@ -21,16 +21,16 @@ public extension RangeReplaceableCollection where Self.Iterator.Element : Equata mutableSelf.insert(element, at: toIndex) } } - + return mutableSelf } } public extension String { - + public func apply(_ patch: [ExtendedPatch]) -> String { var mutableSelf = self - + for change in patch { switch change { case let .insertion(i, element): @@ -46,8 +46,7 @@ public extension String { mutableSelf.insert(element, at: toIndex) } } - + return mutableSelf } } - diff --git a/Sources/ExtendedPatch.swift b/Sources/ExtendedPatch.swift index 5eaddbe..9a39f1e 100644 --- a/Sources/ExtendedPatch.swift +++ b/Sources/ExtendedPatch.swift @@ -9,7 +9,7 @@ enum BoxedDiffAndPatchElement { diffElement: ExtendedDiff.Element, patchElement: SortedPatchElement ) - + var diffElement: ExtendedDiff.Element { switch self { case .move(let de, _, _): @@ -34,7 +34,7 @@ public enum ExtendedPatch { /** Generates a patch sequence. It is a list of steps to be applied to obtain the `to` collection from the `from` one. The sorting function lets you sort the output e.g. you might want the output patch to have insertions first. - + - parameter from: The source collection - parameter to: The target collection - parameter sort: A sorting function @@ -45,17 +45,17 @@ public func extendedPatch( from: T, to: T, sort: ExtendedDiff.OrderedBefore? = nil - ) -> [ExtendedPatch] where T.Iterator.Element : Equatable { +) -> [ExtendedPatch] where T.Iterator.Element: Equatable { return from.extendedDiff(to).patch(from: from, to: to, sort: sort) } extension ExtendedDiff { public typealias OrderedBefore = (_ fst: ExtendedDiff.Element, _ snd: ExtendedDiff.Element) -> Bool - + /** Generates a patch sequence based on the callee. It is a list of steps to be applied to obtain the `to` collection from the `from` one. The sorting function lets you sort the output e.g. you might want the output patch to have insertions first. - + - parameter from: The source collection (usually the source collecetion of the callee) - parameter to: The target collection (usually the target collecetion of the callee) - parameter sort: A sorting function @@ -66,15 +66,15 @@ extension ExtendedDiff { from: T, to: T, sort: OrderedBefore? = nil - ) -> [ExtendedPatch] where T.Iterator.Element : Equatable { - + ) -> [ExtendedPatch] where T.Iterator.Element: Equatable { + let result: [SortedPatchElement] if let sort = sort { result = shiftedPatchElements(from: generateSortedPatchElements(from: from, to: to, sort: sort)) } else { result = shiftedPatchElements(from: generateSortedPatchElements(from: from, to: to)) } - + return result.indices.flatMap { i -> ExtendedPatch? in let patchElement = result[i] if moveIndices.contains(patchElement.sourceIndex) { @@ -93,7 +93,7 @@ extension ExtendedDiff { fatalError() } } - } else if !(i > 0 && moveIndices.contains(result[i-1].sourceIndex)) { + } else if !(i > 0 && moveIndices.contains(result[i - 1].sourceIndex)) { switch patchElement.value { case .deletion(let index): return .deletion(index: index) @@ -101,34 +101,34 @@ extension ExtendedDiff { return .insertion(index: index, element: element) } } - return nil + return nil } } - + func generateSortedPatchElements( from: T, to: T, sort: @escaping OrderedBefore - ) -> [SortedPatchElement] where T.Iterator.Element : Equatable { + ) -> [SortedPatchElement] where T.Iterator.Element: Equatable { let unboxed = boxDiffAndPatchElements( from: from, to: to ).sorted { from, to -> Bool in return sort(from.diffElement, to.diffElement) }.flatMap(unbox) - + return unboxed.indices.map { index -> SortedPatchElement in let old = unboxed[index] return SortedPatchElement( value: old.value, sourceIndex: old.sourceIndex, sortedIndex: index) - }.sorted { (fst, snd) -> Bool in - return fst.sourceIndex < snd.sourceIndex + }.sorted { (fst, snd) -> Bool in + return fst.sourceIndex < snd.sourceIndex } } - - func generateSortedPatchElements(from: T, to: T) -> [SortedPatchElement] where T.Iterator.Element : Equatable { + + func generateSortedPatchElements(from: T, to: T) -> [SortedPatchElement] where T.Iterator.Element: Equatable { let patch = source.patch(from: from, to: to) return patch.indices.map { SortedPatchElement( @@ -138,11 +138,11 @@ extension ExtendedDiff { ) } } - + func boxDiffAndPatchElements( from: T, to: T - ) -> [BoxedDiffAndPatchElement] where T.Iterator.Element : Equatable { + ) -> [BoxedDiffAndPatchElement] where T.Iterator.Element: Equatable { let sourcePatch = generateSortedPatchElements(from: from, to: to) var indexDiff = 0 return elements.indices.map { i in @@ -152,13 +152,13 @@ extension ExtendedDiff { indexDiff += 1 return .move( diffElement: diffElement, - deletion: sourcePatch[sourceIndex[i+indexDiff-1]], - insertion: sourcePatch[sourceIndex[i+indexDiff]] + deletion: sourcePatch[sourceIndex[i + indexDiff - 1]], + insertion: sourcePatch[sourceIndex[i + indexDiff]] ) default: return .single( diffElement: diffElement, - patchElement: sourcePatch[sourceIndex[i+indexDiff]] + patchElement: sourcePatch[sourceIndex[i + indexDiff]] ) } } diff --git a/Sources/GenericPatch.swift b/Sources/GenericPatch.swift index f930689..90105f9 100644 --- a/Sources/GenericPatch.swift +++ b/Sources/GenericPatch.swift @@ -19,7 +19,7 @@ enum EdgeType { func edgeType(from: DoublyLinkedList>, to: DoublyLinkedList>) -> EdgeType { let fromIndex = from.value.sortedIndex let toIndex = to.value.sortedIndex - + if fromIndex == toIndex { return .cycle } else if abs(fromIndex - toIndex) == 1 { @@ -41,7 +41,7 @@ func shiftPatchElement(node: DoublyLinkedList>) { shiftPatchElement(from: nextFrom, to: node) from = nextFrom.previous } - + if let next = node.next { shiftPatchElement(node: next) } @@ -71,6 +71,7 @@ extension SortedPatchElement { sourceIndex: sourceIndex, sortedIndex: sortedIndex) } + func decremented() -> SortedPatchElement { return SortedPatchElement( value: value.decremented(), @@ -80,15 +81,15 @@ extension SortedPatchElement { } extension Patch { - + func incremented() -> Patch { return shiftedIndex(by: 1) } - + func decremented() -> Patch { return shiftedIndex(by: -1) } - + func shiftedIndex(by n: Int) -> Patch { switch self { case let .insertion(index, element): @@ -100,15 +101,15 @@ extension Patch { } func shiftedPatchElements(from sortedPatchElements: [SortedPatchElement]) -> [SortedPatchElement] { - let linkedList = DoublyLinkedList(linkedList: LinkedList(array: sortedPatchElements)) - if let secondElement = linkedList?.next { - shiftPatchElement(node: secondElement) - } - - guard let result = linkedList?.array().sorted(by: { (fst, second) -> Bool in - return fst.sortedIndex < second.sortedIndex - }) else { - return [] - } - return result + let linkedList = DoublyLinkedList(linkedList: LinkedList(array: sortedPatchElements)) + if let secondElement = linkedList?.next { + shiftPatchElement(node: secondElement) + } + + guard let result = linkedList?.array().sorted(by: { (fst, second) -> Bool in + return fst.sortedIndex < second.sortedIndex + }) else { + return [] + } + return result } diff --git a/Sources/LinkedList.swift b/Sources/LinkedList.swift index 3d1c631..62b5963 100644 --- a/Sources/LinkedList.swift +++ b/Sources/LinkedList.swift @@ -2,12 +2,12 @@ class LinkedList { let next: LinkedList? let value: T - + init(next: LinkedList?, value: T) { self.next = next self.value = value } - + init?(array: [T]) { guard let first = array.first else { return nil @@ -26,18 +26,19 @@ class DoublyLinkedList { } return previous.head } + var value: T - + init?(linkedList: LinkedList?) { guard let element = linkedList else { return nil } - + self.value = element.value self.next = DoublyLinkedList(linkedList: element.next) self.next?.previous = self } - + func array() -> Array { if let next = next { return [value] + next.array() @@ -45,4 +46,3 @@ class DoublyLinkedList { return [value] } } - diff --git a/Sources/Patch+Apply.swift b/Sources/Patch+Apply.swift index 3e1e4cc..86d84e4 100644 --- a/Sources/Patch+Apply.swift +++ b/Sources/Patch+Apply.swift @@ -1,5 +1,5 @@ -public extension RangeReplaceableCollection where Self.Iterator.Element : Equatable { +public extension RangeReplaceableCollection where Self.Iterator.Element: Equatable { public func apply(_ patch: [Patch]) -> Self { var mutableSelf = self @@ -20,10 +20,10 @@ public extension RangeReplaceableCollection where Self.Iterator.Element : Equata } public extension String { - + public func apply(_ patch: [Patch]) -> String { var mutableSelf = self - + for change in patch { switch change { case let .insertion(i, element): @@ -34,8 +34,7 @@ public extension String { mutableSelf.remove(at: target) } } - + return mutableSelf } } - diff --git a/Sources/Patch+Sort.swift b/Sources/Patch+Sort.swift index 86b6857..3aee41e 100644 --- a/Sources/Patch+Sort.swift +++ b/Sources/Patch+Sort.swift @@ -1,8 +1,8 @@ /** - Generates arbitrarly sorted patch sequence. It is a list of steps to be applied to obtain the `to` collection from the `from` one. + Generates arbitrarly sorted patch sequence. It is a list of steps to be applied to obtain the `to` collection from the `from` one. The sorting function lets you sort the output e.g. you might want the output patch to have insertions first. - + - parameter from: The source collection - parameter to: The target collection - parameter sort: A sorting function @@ -13,18 +13,18 @@ public func patch( from: T, to: T, sort: Diff.OrderedBefore - ) -> [Patch] where T.Iterator.Element : Equatable { +) -> [Patch] where T.Iterator.Element: Equatable { return from.diff(to).patch(from: from, to: to, sort: sort) } public extension Diff { public typealias OrderedBefore = (_ fst: Diff.Element, _ snd: Diff.Element) -> Bool - + /** Generates arbitrarly sorted patch sequence based on the callee. It is a list of steps to be applied to obtain the `to` collection from the `from` one. The sorting function lets you sort the output e.g. you might want the output patch to have insertions first. - + - parameter from: The source collection (usually the source collecetion of the callee) - parameter to: The target collection (usually the target collecetion of the callee) - parameter sort: A sorting function @@ -35,14 +35,14 @@ public extension Diff { from: T, to: T, sort: OrderedBefore - ) -> [Patch] where T.Iterator.Element : Equatable { + ) -> [Patch] where T.Iterator.Element: Equatable { let shiftedPatch = patch(from: from, to: to) return shiftedPatchElements(from: sortedPatchElements( from: shiftedPatch, sortBy: sort )).map { $0.value } } - + private func sortedPatchElements(from source: [Patch], sortBy areInIncreasingOrder: OrderedBefore) -> [SortedPatchElement] { let sorted = indices.map { (self[$0], $0) } .sorted { areInIncreasingOrder($0.0, $1.0) } @@ -56,5 +56,4 @@ public extension Diff { return fst.sourceIndex < snd.sourceIndex }) } - } diff --git a/Sources/Patch.swift b/Sources/Patch.swift index e733f8c..00e0bd3 100644 --- a/Sources/Patch.swift +++ b/Sources/Patch.swift @@ -6,7 +6,7 @@ public enum Patch { case insertion(index: Int, element: Element) /// A single patch step containing a deletion index case deletion(index: Int) - + func index() -> Int { switch self { case let .insertion(index, _): @@ -18,10 +18,10 @@ public enum Patch { } public extension Diff { - + /** Generates a patch sequence based on a diff. It is a list of steps to be applied to obtain the `to` collection from the `from` one. - + - parameter from: The source collection (usually the source collecetion of the callee) - parameter to: The target collection (usually the target collecetion of the callee) - complexity: O(N) @@ -30,13 +30,13 @@ public extension Diff { public func patch( from: T, to: T - ) -> [Patch] where T.Iterator.Element : Equatable { + ) -> [Patch] where T.Iterator.Element: Equatable { var shift = 0 return map { element in switch element { case let .delete(at): shift -= 1 - return .deletion(index: at+shift+1) + return .deletion(index: at + shift + 1) case let .insert(at): shift += 1 return .insertion(index: at, element: to.itemOnStartIndex(advancedBy: at)) @@ -46,17 +46,17 @@ public extension Diff { } /** - Generates a patch sequence. It is a list of steps to be applied to obtain the `to` collection from the `from` one. - - - parameter from: The source collection - - parameter to: The target collection - - complexity: O((N+M)*D) - - returns: A sequence of steps to obtain `to` collection from the `from` one. + Generates a patch sequence. It is a list of steps to be applied to obtain the `to` collection from the `from` one. + + - parameter from: The source collection + - parameter to: The target collection + - complexity: O((N+M)*D) + - returns: A sequence of steps to obtain `to` collection from the `from` one. */ public func patch( from: T, to: T - ) -> [Patch] where T.Iterator.Element : Equatable { +) -> [Patch] where T.Iterator.Element: Equatable { return from.diff(to).patch(from: from, to: to) }