├── .gitignore ├── logo.png ├── Documentation └── diffConcept1.png ├── Tests ├── LinuxMain.swift └── FastDiffTests │ ├── XCTestManifests.swift │ ├── FastDiffComplexityTests.swift │ ├── FastDiffTests.swift │ └── FastDiffDiffAllLevelTests.swift ├── FastDiff.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcuserdata │ │ ├── vkandel.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ │ └── mkoczorek.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ ├── WorkspaceSettings.xcsettings │ │ └── swiftpm │ │ └── Package.resolved ├── xcuserdata │ ├── mkoczorek.xcuserdatad │ │ └── xcschemes │ │ │ ├── xcschememanagement.plist │ │ │ └── FastDiff-Package.xcscheme │ └── vkandel.xcuserdatad │ │ ├── xcschemes │ │ └── xcschememanagement.plist │ │ └── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist ├── FastDiff_Info.plist ├── AlgoChecker_Info.plist ├── FastDiffLib_Info.plist ├── FastDiffTests_Info.plist ├── xcshareddata │ └── xcschemes │ │ ├── FastDiffTests.xcscheme │ │ └── FastDiff.xcscheme └── project.pbxproj ├── Package.resolved ├── FastDiff ├── FastDiff.h └── Info.plist ├── Package.swift ├── FastDiff.podspec ├── LICENSE.md ├── Sources └── FastDiff │ ├── InternalDiff.swift │ ├── Diffable.swift │ └── DiffingAlgorithm.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | .swiftpm 5 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kandelvijaya/FastDiff/HEAD/logo.png -------------------------------------------------------------------------------- /Documentation/diffConcept1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kandelvijaya/FastDiff/HEAD/Documentation/diffConcept1.png -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import FastDiffTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += FastDiffTests.allTests() 7 | XCTMain(tests) -------------------------------------------------------------------------------- /FastDiff.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Tests/FastDiffTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(FastDiffTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /FastDiff.xcodeproj/project.xcworkspace/xcuserdata/vkandel.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kandelvijaya/FastDiff/HEAD/FastDiff.xcodeproj/project.xcworkspace/xcuserdata/vkandel.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /FastDiff.xcodeproj/project.xcworkspace/xcuserdata/mkoczorek.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kandelvijaya/FastDiff/HEAD/FastDiff.xcodeproj/project.xcworkspace/xcuserdata/mkoczorek.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /FastDiff.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /FastDiff.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "AlgoChecker", 6 | "repositoryURL": "git@github.com:kandelvijaya/AlgorithmChecker.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "3c786303ec2cdd0018b77f1597d6e38e0448039a", 10 | "version": "0.1.5" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /FastDiff/FastDiff.h: -------------------------------------------------------------------------------- 1 | // 2 | // FastDiff.h 3 | // FastDiff 4 | // 5 | // Created by Vijaya Prakash Kandel on 7/9/19. 6 | // 7 | 8 | #import 9 | 10 | //! Project version number for FastDiff. 11 | FOUNDATION_EXPORT double FastDiffVersionNumber; 12 | 13 | //! Project version string for FastDiff. 14 | FOUNDATION_EXPORT const unsigned char FastDiffVersionString[]; 15 | 16 | // In this header, you should import all the public headers of your framework using statements like #import 17 | 18 | 19 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.2 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "FastDiff", 7 | products: [ 8 | .library( 9 | name: "FastDiff", 10 | targets: ["FastDiff"]), 11 | ], 12 | dependencies: [ 13 | .package(url: "https://github.com/kandelvijaya/AlgorithmChecker.git", from: "0.1.0"), 14 | ], 15 | targets: [ 16 | .target( 17 | name: "FastDiff", 18 | dependencies: []), 19 | .testTarget( 20 | name: "FastDiffTests", 21 | dependencies: ["FastDiff", "AlgoChecker"]), 22 | ] 23 | ) 24 | -------------------------------------------------------------------------------- /FastDiff.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "AlgoChecker", 6 | "repositoryURL": "https://github.com/kandelvijaya/AlgorithmChecker.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "3c786303ec2cdd0018b77f1597d6e38e0448039a", 10 | "version": "0.1.5" 11 | } 12 | }, 13 | { 14 | "package": "Randomizer", 15 | "repositoryURL": "https://github.com/kandelvijaya/Randomizer.git", 16 | "state": { 17 | "branch": "master", 18 | "revision": "d7f6aa7980f87fd0a6e7369bf5f37703ee8e91ec", 19 | "version": null 20 | } 21 | } 22 | ] 23 | }, 24 | "version": 1 25 | } 26 | -------------------------------------------------------------------------------- /FastDiff.xcodeproj/xcuserdata/mkoczorek.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | FastDiff-Package.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | FastDiff.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 1 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | FastDiff::FastDiff 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /FastDiff/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /FastDiff.xcodeproj/FastDiff_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /FastDiff.xcodeproj/AlgoChecker_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /FastDiff.xcodeproj/FastDiffLib_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /FastDiff.xcodeproj/FastDiffTests_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | BNDL 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Tests/FastDiffTests/FastDiffComplexityTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FastDiffComplexityTests.swift 3 | // FastDiff 4 | // 5 | // Created by Vijaya Prakash Kandel on 21.10.18. 6 | // 7 | 8 | import Foundation 9 | import XCTest 10 | import AlgoChecker 11 | @testable import FastDiff 12 | 13 | final class FastDiffComplexityTests: XCTestCase { 14 | 15 | func test_fastDiff_hasLinearTimeComplexity() { 16 | // 1. Wrap Algorithm in Operation 17 | let algoOperation = AlgorithmChecker.Operation { (inputProvider, completion) in 18 | let input1: [Int] = inputProvider.input() 19 | let input2: [Int] = inputProvider.input() 20 | let result = diff(input1, input2) 21 | completion(result.count) 22 | } 23 | 24 | // 3. Find/assert algorithm complexity 25 | var checker = AlgorithmChecker() 26 | let result = checker.assert(algorithm: algoOperation, has: .linear, tolerance: .low) 27 | 28 | XCTAssertEqual(result, true) 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /FastDiff.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | # 1 4 | s.platform = :ios 5 | s.ios.deployment_target = '9.0' 6 | s.name = "FastDiff" 7 | s.summary = "FastDiff is a general purpose diffing algorithm with Linear complexity O(n)" 8 | s.requires_arc = true 9 | 10 | # 2 11 | s.version = "1.1.1" 12 | 13 | # 3 14 | s.license = { :type => "MIT", :file => "LICENSE" } 15 | 16 | # 4 17 | s.author = { "Vijaya Prakash Kandel" => "kandelvijaya@gmail.com" } 18 | 19 | # 5 20 | s.homepage = "https://github.com/kandelvijaya/FastDiff" 21 | 22 | # 6 - Replace this URL with your own Git URL from "Quick Setup" 23 | s.source = { :git => "https://github.com/kandelvijaya/FastDiff.git", 24 | :tag => "#{s.version}" } 25 | 26 | # 7 27 | # s.framework = "UIKit" 28 | 29 | # 8 30 | s.source_files = "Sources/**/*.{swift}" 31 | 32 | # 9 33 | #s.resources = "RWPickFlavor/**/*.{png,jpeg,jpg,storyboard,xib,xcassets}" 34 | 35 | # 10 36 | s.swift_version = "4.2" 37 | 38 | end 39 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Vijaya Prakash Kandel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /FastDiff.xcodeproj/xcuserdata/vkandel.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | FastDiff.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 1 11 | 12 | FastDiffTests.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | AlgoChecker::AlgoChecker 21 | 22 | primary 23 | 24 | 25 | AlgoChecker::SwiftPMPackageDescription 26 | 27 | primary 28 | 29 | 30 | FastDiff::FastDiff 31 | 32 | primary 33 | 34 | 35 | FastDiff::FastDiffLib 36 | 37 | primary 38 | 39 | 40 | FastDiff::FastDiffPackageTests::ProductTarget 41 | 42 | primary 43 | 44 | 45 | FastDiff::FastDiffTests 46 | 47 | primary 48 | 49 | 50 | FastDiff::SwiftPMPackageDescription 51 | 52 | primary 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /Sources/FastDiff/InternalDiff.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InternalDiff.swift 3 | // FastDiff 4 | // 5 | // Created by Vijaya Prakash Kandel on 25.09.18. 6 | // Copyright © 2018 com.kandelvijaya. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public func internalDiff(from diffOperations: [DiffOperation.Simple]) -> [(offset: Int, operations: [DiffOperation.Simple])] { 12 | var accumulator = [(offset: Int, operations: [DiffOperation.Simple])]() 13 | for operation in diffOperations { 14 | switch operation { 15 | case let .update(oldContainer, newContainer, atIndex): 16 | let oldChildItems = oldContainer.innerDiffableItems 17 | let newChildItems = newContainer.innerDiffableItems 18 | let internalDiff = orderedOperation(from: diff(oldChildItems, newChildItems)) 19 | let output = (atIndex, internalDiff) 20 | accumulator.append(output) 21 | default: 22 | break 23 | } 24 | } 25 | return accumulator 26 | } 27 | 28 | 29 | /// Calculates diff from entire graph going deeper as it finds `update` on container. 30 | /// It is greedy algorithm. 31 | /// - NOTE: Profile when running on main thread. 32 | /// 33 | /// - Complexity:- O(allEdges * nlog(n)) 34 | public func diffAllLevel(_ oldContent: [T], _ newContent: [T]) -> [DiffOperation] where T: Diffable, T.InternalItemType == T { 35 | if oldContent.isEmpty && newContent.isEmpty { return [] } 36 | var accumulator: [DiffOperation] = [] 37 | 38 | let thisLevelDiff = diff(oldContent, newContent) 39 | for oneDiffItem in thisLevelDiff { 40 | // We ignore the index. 41 | if case let .update(old, new, _) = oneDiffItem { 42 | accumulator = accumulator + diffAllLevel(old.innerDiffableItems, new.innerDiffableItems) 43 | } else { 44 | accumulator.append(oneDiffItem) 45 | } 46 | } 47 | return accumulator 48 | } 49 | -------------------------------------------------------------------------------- /FastDiff.xcodeproj/xcshareddata/xcschemes/FastDiffTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 16 | 18 | 24 | 25 | 26 | 27 | 28 | 38 | 39 | 45 | 46 | 48 | 49 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /FastDiff.xcodeproj/xcshareddata/xcschemes/FastDiff.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 44 | 45 | 51 | 52 | 58 | 59 | 60 | 61 | 63 | 64 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /Sources/FastDiff/Diffable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Diffable.swift 3 | // FastDiff 4 | // 5 | // Created by Vijaya Prakash Kandel on 18.06.18. 6 | // Copyright © 2018 com.kandelvijaya. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Conforming types can be used to calculate `diff` 12 | public protocol Diffable: Hashable { 13 | 14 | /// Used to represent the internalItemType that represents another level. 15 | /// By default, this will be the same type as the conforming i.e. without customization. 16 | associatedtype InternalItemType: Diffable = Self 17 | 18 | 19 | 20 | /** 21 | **Defaults** to return empty array; making it non-container type. 22 | Allows Diffable to represent a Graph/Tree structure. 23 | 24 | Items can be in update state either object pointed by pointer changed or their internalItems aren't the same. 25 | 26 | ## Note 27 | Equality and hashValue stay as is 28 | 29 | ## Cases 30 | 2 Diffable items determined to be in **update** state if 31 | - both items are not leaves (non-container type) in the graph 32 | - are not equal (Equality considers every property in Diffable) 33 | - both have the same diffHash (diffHash should exclude innerDiffableItems) 34 | */ 35 | var innerDiffableItems: [InternalItemType] { get } 36 | 37 | /** 38 | **Deafults** to returning `hashValue` when this type conforms to `Equatable` 39 | 40 | Only conform and customize conformance if you intend to represnet this Diffable type as Graph/Tree. 41 | When you conform; leave out `innerDiffableItems`s hashValue. 42 | 43 | ## Two Diffable tiems 44 | - with same diffHash 45 | - and not equal is considered update 46 | - and equal is considered exact replicated item. 47 | - with different diffHash 48 | - cannot be equal (impossible) 49 | - are considered 2 different items (delete then insert) 50 | */ 51 | var diffHash: Int { get } 52 | 53 | } 54 | 55 | 56 | extension Diffable { 57 | 58 | public var diffHash: Int { return self.hashValue } 59 | public var innerDiffableItems: [InternalItemType] { return [] } 60 | 61 | } 62 | 63 | 64 | extension Array: Diffable where Element: Diffable { 65 | 66 | public var diffHash: Int { 67 | return reduce(0) { $0 ^ $1.diffHash } 68 | } 69 | 70 | } 71 | 72 | extension String: Diffable {} 73 | extension Int: Diffable {} 74 | extension Character: Diffable {} 75 | extension UInt: Diffable {} 76 | extension URL: Diffable {} 77 | extension Substring: Diffable {} 78 | extension Double: Diffable {} 79 | extension Float: Diffable {} 80 | extension Bool: Diffable {} 81 | 82 | -------------------------------------------------------------------------------- /Tests/FastDiffTests/FastDiffTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import FastDiff 3 | 4 | struct Cat: Hashable, Diffable { 5 | let name: String 6 | } 7 | 8 | struct Person { 9 | let name: String 10 | let age: Int 11 | let pets: [Cat] 12 | } 13 | 14 | 15 | extension Person: Hashable, Diffable { 16 | 17 | typealias InternalItemType = Cat 18 | 19 | var diffHash: Int { 20 | return name.hashValue ^ age.hashValue 21 | } 22 | 23 | var innerDiffableItems: [Cat] { 24 | return pets 25 | } 26 | } 27 | 28 | 29 | final class FastDiffTests: XCTestCase { 30 | 31 | func test_whenEmptyIntArrayIsDiffedWithSingleElementArray_thenThereIs1Insertion() { 32 | let opers = diff([Int](), [1]) 33 | XCTAssertEqual(opers.count, 1) 34 | XCTAssertEqual(opers[0], .add(1,0)) 35 | } 36 | 37 | func test_whenNewArrayIsEmpty_thenEverythingIsDeleted() { 38 | let opers = diff([1],[]) 39 | XCTAssertEqual(opers.count, 1) 40 | XCTAssertEqual(opers[0], .delete(1,0)) 41 | } 42 | 43 | func test_whenBothEmptyArrayAreDiffed_thenOperationsIsEmpty() { 44 | XCTAssertEqual(diff([Int](),[]), []) 45 | } 46 | 47 | func test_whenSingletonArray_whereSameItemIsChnaged_thenItIsUpdate() { 48 | XCTAssertEqual(diff([1], [2]), [.delete(1,0), .add(2,0)]) 49 | } 50 | 51 | func test_seemsLikeMove() { 52 | let opers = diff([1,2,3], [2,3]) 53 | XCTAssertEqual(opers.count, 1) 54 | XCTAssertEqual(opers, [.delete(1,0)]) 55 | } 56 | 57 | func test_seemsLikeMoveRight() { 58 | let opers = diff([1,2], [4,1,2]) 59 | XCTAssertEqual(opers.count, 1) 60 | XCTAssertEqual(opers, [.add(4,0)]) 61 | } 62 | 63 | func test_moveCrissCross() { 64 | let opers = diff([1,2], [2,1]) 65 | XCTAssertEqual(opers.count, 2) 66 | XCTAssertEqual(opers, [.move(2,1,0), .move(1,0,1)]) 67 | } 68 | 69 | func test_whenContainerTypesAreDiffed_thenItProducesUpdate() { 70 | let operation = diff([x], [y]) 71 | XCTAssertEqual(operation.first!, .update(x, y, 0)) 72 | } 73 | 74 | func test_whenContainerTypesAreDiffed_thereIsUpdateAndEachContainsCollectionOfDiffable_thenInternalDiffCanBePerformed() { 75 | let operation = diff([x], [y]) 76 | XCTAssertEqual(operation.first!, .update(x, y, 0)) 77 | let internalDiff = diff(x.innerDiffableItems, y.innerDiffableItems) 78 | XCTAssertEqual(internalDiff.count, 1) 79 | XCTAssertEqual(internalDiff.first!, .add(Cat(name: "meow jr."), 1)) 80 | } 81 | 82 | func test_when2IdenticalCollectionsAreDiffed_thenThereIs0Change() { 83 | let operation = diff([301, 301], [301, 301]) 84 | XCTAssertEqual(operation.count, 0) 85 | } 86 | 87 | let x = Person(name: "BJ", age: -1, pets: [Cat(name: "meow")]) 88 | let y = Person(name: "BJ", age: -1, pets: [Cat(name: "meow"), 89 | Cat(name: "meow jr.")]) 90 | 91 | static var allTests = [ 92 | ("test move", test_seemsLikeMove), 93 | ("test crisscross", test_moveCrissCross), 94 | ("test move right", test_seemsLikeMoveRight), 95 | // TODO:- include more tests 96 | ] 97 | 98 | } 99 | -------------------------------------------------------------------------------- /FastDiff.xcodeproj/xcuserdata/mkoczorek.xcuserdatad/xcschemes/FastDiff-Package.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 55 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 74 | 76 | 77 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /Tests/FastDiffTests/FastDiffDiffAllLevelTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FastDiffDiffAllLevelTests.swift 3 | // FastDiffTests 4 | // 5 | // Created by Vijaya Prakash Kandel on 12.02.21. 6 | // 7 | 8 | import XCTest 9 | @testable import FastDiff 10 | import Randomizer 11 | 12 | extension FastDiffTests { 13 | 14 | func test_whenDiffingAllLevelsForFlatItems_diffResultEqualsToAllDiff() { 15 | let old = [1,2,3] 16 | let new = [1,2,4] 17 | let diffNormal = diff(old, new) 18 | let diffAllLevels = diffAllLevel(old, new) 19 | XCTAssertEqual(diffNormal, diffAllLevels) 20 | } 21 | 22 | func test_whenNestedItemIsProvided_thenDiffAllLevelsWillIdentifyNestedChanges() { 23 | let nodeA = Node(name: .random, metadata: .random, edges: [ 24 | Node(name: .random, metadata: .random, edges: []) 25 | ]) 26 | 27 | let nodeB = Node(name: nodeA.name, metadata: nodeA.metadata, edges: [ 28 | nodeA.edges.first!, 29 | Node(name: .random, metadata: .random, edges: []) 30 | ]) 31 | 32 | let diffSingleLevel = diff([nodeA], [nodeB]) 33 | let diffAll = diffAllLevel([nodeA], [nodeB]) 34 | 35 | XCTAssertEqual(diffSingleLevel.count, 1) 36 | guard case .update(_,_,_) = diffSingleLevel.first else { 37 | XCTFail("Since the name and meta on the parent level are same; its a container update") 38 | return 39 | } 40 | 41 | XCTAssertEqual(diffAll.count, 1) 42 | guard case let .add(item, index) = diffAll.first! else { 43 | XCTFail("Should be Add at second position on children items. Nothing else changed") 44 | return 45 | } 46 | XCTAssertEqual(index, 1) 47 | XCTAssertEqual(item.name, nodeB.edges.last!.name) 48 | XCTAssertEqual(item.metadata, nodeB.edges.last!.metadata) 49 | } 50 | 51 | func test_when3LevelDownTreeIsDiffedOnAllLevel_thenItWorks() { 52 | let nodeA = Node(name: .random, metadata: .random, edges: [ 53 | Node(name: .random, metadata: .random, edges: [ 54 | Node(name: .random, metadata: .random, edges: []) 55 | ]) 56 | ]) 57 | 58 | let nodeB = Node(name: nodeA.name, metadata: nodeA.metadata, edges: [ 59 | Node(name: nodeA.edges.first!.name, metadata: nodeA.edges.first!.metadata, edges: [ 60 | Node(name: nodeA.edges.first!.edges.first!.name, metadata: nodeA.edges.first!.edges.first!.metadata, edges: []), 61 | Node(name: .random, metadata: .random, edges: []) // new item 62 | ]) 63 | ]) 64 | 65 | let diffSingleLevel = diff([nodeA], [nodeB]) 66 | let diffAll = diffAllLevel([nodeA], [nodeB]) 67 | 68 | XCTAssertEqual(diffSingleLevel.count, 1) 69 | guard case .update(_,_,_) = diffSingleLevel.first else { 70 | XCTFail("Since the name and meta on the parent level are same; its a container update") 71 | return 72 | } 73 | 74 | XCTAssertEqual(diffAll.count, 1) 75 | guard case let .add(item, index) = diffAll.first! else { 76 | XCTFail("Should be Add at second position on children items. Nothing else changed") 77 | return 78 | } 79 | XCTAssertEqual(index, 1) 80 | XCTAssertEqual(item.name, nodeB.edges.first!.edges.last!.name) 81 | XCTAssertEqual(item.metadata, nodeB.edges.first!.edges.last!.metadata) 82 | } 83 | 84 | } 85 | 86 | struct Node { 87 | let name: String 88 | let metadata: Int 89 | let edges: [Node] 90 | } 91 | 92 | extension Node: Diffable { 93 | 94 | var innerDiffableItems: [Node] { 95 | return edges 96 | } 97 | 98 | var diffHash: Int { 99 | name.hashValue ^ metadata.hashValue 100 | } 101 | } 102 | 103 | -------------------------------------------------------------------------------- /FastDiff.xcodeproj/xcuserdata/vkandel.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 22 | 23 | 24 | 26 | 39 | 40 | 41 | 43 | 56 | 57 | 58 | 60 | 73 | 74 | 75 | 77 | 90 | 91 | 92 | 94 | 107 | 108 | 109 | 111 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

3 |
4 | 5 | ----------------- 6 | 7 | # Fast Diff ![CI status](https://img.shields.io/badge/build-passing-brightgreen.svg) 8 | 9 | General purpose, fast diff algorithm supporting [m] level nested diffs. 10 | 11 | ## Time Complexity 12 | - Linear i.e. O(n) 13 | 14 | ## Why? 15 | 1. Faster than the mainstream algorithm. Most diffing algorithm are O(nlogn) or O(n.m). This one is linear O(n). 16 | 2. Most algorithm solve Least Common Subsequence problem which has hard to grasp implementation. This uses 6 simple looping passes. 17 | 3. Supports nested diffing (if you desire) 18 | 19 | ## Installation 20 | ### Via cocoapods 21 | ```swift 22 | pod 'FastDiff' 23 | ``` 24 | And then in the terminal `pod update`. If you are new to cocoapods please check out [Cocoapods Installation](https://guides.cocoapods.org/using/using-cocoapods) 25 | 26 | ### Via Swift Package Manager 27 | Declare the dependency in the swift `Package.swift` file like such: 28 | ```swift 29 | dependencies: [ 30 | ///.... other deps 31 | .package(url: "https://www.github.com/kandelvijaya/FastDiff", from: "1.0.0"), 32 | ] 33 | ``` 34 | 35 | Execute the update command `swift package update` and then `swift package generate-xcodeproj`. 36 | 37 | ## Running the tests 38 | 39 | Go to the source directory, and run: 40 | ```swift 41 | $ swift test 42 | ``` 43 | 44 | ## Usage 45 | 46 | ### Algorithm & Verification 47 | ```swift 48 | let oldModels = ["apple", "microsoft"] 49 | let newModels = ["apple", "microsoft", "tesla"] 50 | 51 | 52 | /// Algorithm 53 | let changeSet = diff(oldModels, newModels) 54 | // [.addition("tesla", at: 2)] 55 | 56 | 57 | /// Verification 58 | oldModels.merged(with: changeSet) == newModels 59 | // true 60 | ``` 61 | 62 |
63 |

64 |
65 | 66 | 67 | Note that `diff` produces changeset that can't be merged into old collections as is, most of the times. 68 | The changeset has to be `ordered` in-order for successful merge. This is also useful if you want to 69 | apply changeset to `UITableView` or `UICollectionView`. 70 | 71 | ```swift 72 | let chnageSet = diff(["A","B"], [“C”,"D"]) 73 | // [.delete("A",0), .delete("B",1), .add("C",0), .add(“D",1)] 74 | 75 | let orderedChangeSet = orderedOperation(from: changeSet) 76 | // [.delete("B",1), .delete("A",0), .add("C",0), .add("D",1)] 77 | 78 | ``` 79 | 80 | ### Advanced usage & notes 81 | 1. This algorithm works accurately with value types `Struct`'s. Please refrain from using reference type (`Class` instance). When you must use class instance / object, you might get more updates than you expect. If you want to resolve this issue for your use case please DM me www.twitter.com/kandelvijaya 82 | 2. Tree diffing is possible. However not something the library encourages due to added complexity O(n^2). If you so choose to diff then please use `diffAllLevel(,)` 83 | 3. The complexity of Graph diffing depends on graph structure. For Trees, its O(n^2). Please note that this change set is not mergeable to the original tree. To circumvent this limitation, use a node with indexes or indepath that points to the graph position implicitly. 84 | 85 | ### Concept and advanced usage in List View Controller (iOS) 86 | Please check out this presentation slides that I gave at [@mobiconf 2018](https://drive.google.com/file/d/1eY0k_5sHBDgK6Qx6-VR3HTmCQEi9qaW3/view?usp=sharing) 87 | 88 | 89 | 90 | ## Why is nested diffing important? Tutorial/HowTo 91 | Say you got a list of Component where each is defined as: 92 | 93 | ```swift 94 | struct Component { 95 | let title: String 96 | let footer: FooterViewModel? // useful on top levels 97 | let children: [Component]? // nil when its a leaf. 98 | let icons_sf: [String] 99 | } 100 | ``` 101 | 102 | Say we got this model represented in the UI using CollectionView sections. `Today` and `Tomorrow` are represented by SectionHeaderSupplemenratyViews and so are corresponding footers. The internalItems are represented by `TaskCell`. User has the ability to add new task using NavBar Button. 103 | 104 | ```swift 105 | let old = [ 106 | 107 | Component(title: "Today", footer: .init(), icons_sf: ["1.fill", "2.circle"], children: [ 108 | Component(title: "Go to supermarket", footer: nil, icons_sf: ["sf.cucumber"], children: nil), 109 | Component(title: "Make breakfast", footer: nil, icons_sf: ["sf.avocado"], children: nil) 110 | ]), 111 | 112 | Component(title: "Tomorrow", footer: .init(), icons_sf: ["1.fill", "2.circle"], children: [ 113 | Component(title: "Work on FastDiff", footer: nil, icons_sf: ["sf.chopsticks"], children: nil), 114 | Component(title: "SwiftUI TODO list for macos", footer: nil, icons_sf: ["sf.pen"], children: nil) 115 | ]) 116 | 117 | ] 118 | ``` 119 | 120 | Say user adds a new task item to Todays entry therefore changing the new model becomes: 121 | ```swift 122 | let new = [ 123 | 124 | Component(title: "Today", footer: .init(), icons_sf: ["1.fill", "2.circle"], children: [ 125 | Component(title: "Go to supermarket", footer: nil, icons_sf: ["sf.cucumber"], children: nil), 126 | Component(title: "Make breakfast", footer: nil, icons_sf: ["sf.avocado"], children: nil), 127 | 128 | /// newly added 129 | Component(title: "Buy PS5 from amazon", footer: nil, icons_sf: ["sf.play"], children: nil), 130 | ]), 131 | 132 | Component(title: "Tomorrow", footer: .init(), icons_sf: ["1.fill", "2.circle"], children: [ 133 | Component(title: "Work on FastDiff", footer: nil, icons_sf: ["sf.chopsticks"], children: nil), 134 | Component(title: "SwiftUI TODO list for macos", footer: nil, icons_sf: ["sf.pen"], children: nil) 135 | ]) 136 | 137 | ] 138 | ``` 139 | 140 | We assume `Component: Diffable` 141 | 142 | ### What is your expectation when you perform `diff(old, new)`? 143 | There can be 2 potential solutions: 144 | 145 | 1. `[.delete(item: old.0, at: 0), insert(item: new.0, at 0)]` 146 | 147 | - diffable conformance can look like this: 148 | ```swift 149 | extension Component: Diffable {} 150 | ``` 151 | - UI side: you would remove the entire 1st section, construct new section and insert it. This throws away the enitre section when we know 2 internal items (cell) didn't change across old and new. 152 | - We wasted a bit of resource. 153 | - We won't get insertion animation for the excat change. 154 | 155 | 2. `[.update(at: 0, old: old.0, new: new.0)]` 156 | - diffable conformance will look like this: 157 | ```swift 158 | extension Component: Diffable, Equatable { 159 | 160 | var diffHash: Int { 161 | /// excludes innerDiffItems 162 | return title.hashValue ^ footer.hashValue ^ icons_sf.hashValue 163 | } 164 | 165 | var innerDiffableItems: [Component] { 166 | return children ?? [] 167 | } 168 | 169 | } 170 | ``` 171 | - UI side: when receiving `.update(,,,)` on section level, we can perform diff on internal items like so `diff(old.innerDiffableItems, new.innerDiffableItems)` to receive exact changes on cell level which can then be patched to `section.performBatchUpdate` 172 | - New task addition is animated, its the only thing that changed on the UI 173 | - Effecient patching of changed content. 174 | 175 | 176 | ## Authors 177 | 178 | 1. @kandelvijaya (https://twitter.com/kandelvijaya) 179 | 180 | ## License 181 | 182 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 183 | 184 | ## Acknowledgments 185 | 186 | * Inspired by Paul Heckel's paper & algorithm 187 | -------------------------------------------------------------------------------- /Sources/FastDiff/DiffingAlgorithm.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Diff.swift 3 | // FastDiff 4 | // 5 | // Created by Vijaya Prakash Kandel on 18.06.18. 6 | // Copyright © 2018 com.kandelvijaya. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// During the diff, we are mostly interested in this combination 12 | /// 1. one - one 13 | /// 2. one/many - one/many 14 | /// `zero` is the base or non-existing line count 15 | enum OccuranceCount { 16 | 17 | case zero, one, many 18 | 19 | func increment() -> OccuranceCount { 20 | switch self { 21 | case .zero: 22 | return .one 23 | case .one, .many: 24 | return .many 25 | } 26 | } 27 | 28 | } 29 | 30 | /// SymEntry is modelled as reference type, so that we can keep 31 | /// a pointer NOT either a entire copy (value type) or 32 | /// unsafe raw pointer which requires manual pointer dance. 33 | class SymEntry { 34 | 35 | /// Occurance in old file 36 | var oc: OccuranceCount = .zero 37 | 38 | /// Occurance in new file 39 | var nc: OccuranceCount = .zero 40 | 41 | /// Line number in old set 42 | /// This only makes sense if OC == NC == .one 43 | var olno: Int = -1 44 | 45 | /// Detects if line is identically unique across both changes 46 | func isIdenticallyUnqiueAcrossChanges() -> Bool { 47 | return oc == nc && nc == .one 48 | } 49 | 50 | } 51 | 52 | 53 | /// Represents either a SymEntry (pointer by class) or 54 | /// LineNumber in another change set if the changes were resolved 55 | enum LineLookup { 56 | /// SymEntry should be a reference/pointer for efficiency 57 | case sym(SymEntry) 58 | case lineNumber(Int) 59 | 60 | func pointsToSameSymEntry(as anotherLookup: LineLookup) -> Bool { 61 | if case let (.sym(s1), .sym(s2)) = (self, anotherLookup) { 62 | /// pointer check 63 | return s1 === s2 64 | } 65 | return false 66 | } 67 | 68 | } 69 | 70 | 71 | /// Kinds of operation 72 | public enum DiffOperation { 73 | case add(T, Int) 74 | case delete(T, Int) 75 | case move(T, Int, Int) 76 | case update(T,T,Int) 77 | 78 | public enum Simple { 79 | case add(T,Int) 80 | case delete(T, Int) 81 | case update(T,T,Int) 82 | } 83 | 84 | } 85 | 86 | extension DiffOperation: Equatable where T: Equatable { } 87 | 88 | // MARK:- Playground view 89 | 90 | extension DiffOperation: CustomStringConvertible { 91 | 92 | public var description: String { 93 | switch self { 94 | case let .add(v, i): 95 | return "A(\(v)@\(i))" 96 | case let .delete(v, i): 97 | return "D(\(v)@\(i))" 98 | case let .move(v,i,j): 99 | return "M(\(v)from\(i)->\(j))" 100 | case let .update(v1, v2, i): 101 | return "U(\(v1)=>\(v2)@\(i))" 102 | } 103 | } 104 | 105 | } 106 | 107 | 108 | extension SymEntry: CustomStringConvertible { 109 | var description: String { 110 | return "{oc: \(oc), nc: \(nc), olno: \(olno)}" 111 | } 112 | } 113 | 114 | 115 | extension LineLookup: CustomStringConvertible { 116 | var description: String { 117 | switch self { 118 | case let .lineNumber(l): 119 | return "L(\(l))" 120 | case let .sym(e): 121 | return "S(\(e))" 122 | } 123 | } 124 | } 125 | 126 | 127 | 128 | public func diff(_ oldContent: [T], _ newContent: [T]) -> [DiffOperation] where T: Diffable { 129 | 130 | // Treats the same/equal/identical collections unchanged to not be used for diffing 131 | // diff([1,1], [1,1]) ==> no change 132 | if oldContent.hashValue == newContent.hashValue && oldContent.diffHash == newContent.diffHash && oldContent == newContent { return [] } 133 | if oldContent.isEmpty && newContent.isEmpty { return [] } 134 | 135 | typealias DiffHash = Int 136 | 137 | var symTable: [DiffHash: SymEntry] = [:] 138 | 139 | //1. go over new and create table 140 | //2. go over old and create/edit table 141 | //3. go over new, lookup table and detect unique occurance 142 | //4. go over new, check block of changes in ascending order 143 | //5. go over new, check block of changes in descending order 144 | //6. go over old and get deletions + go over new and 145 | // find insertion & deletion 146 | 147 | /// LineLookup map for each index in old content 148 | /// for index `i` in old content, acess LineLookup with `oas[i]` 149 | var oas: [LineLookup] = [] 150 | 151 | /// LineLookup map for each index in new content 152 | /// for index `i` in new content, acess LineLookup with `nas[i]` 153 | var nas: [LineLookup] = [] 154 | 155 | 156 | /// Pass1 157 | /// Iterate over new content and build both `symEntry`s and `nas` 158 | for content in newContent { 159 | let entry = symTable[content.diffHash] ?? SymEntry() 160 | entry.nc = entry.nc.increment() 161 | symTable[content.diffHash] = entry 162 | nas.append(LineLookup.sym(entry)) 163 | } 164 | 165 | 166 | /// Pass2 167 | /// Iterate over old content and do the same as pass1 168 | for (index, content) in oldContent.enumerated() { 169 | let entry = symTable[content.diffHash] ?? SymEntry() 170 | entry.oc = entry.oc.increment() 171 | entry.olno = index 172 | symTable[content.diffHash] = entry 173 | oas.append(LineLookup.sym(entry)) 174 | } 175 | 176 | 177 | /// Pass 3 178 | /// Detect unique line pair across both new and old content 179 | /// if OC == NC == .one, then for nas[i] substitute olno from its sym Emtry 180 | for (index, lookup) in nas.enumerated() { 181 | if case let .sym(entry) = lookup, entry.isIdenticallyUnqiueAcrossChanges() { 182 | nas[index] = .lineNumber(entry.olno) 183 | oas[entry.olno] = .lineNumber(index) 184 | } 185 | } 186 | 187 | 188 | /// Pass 4 189 | /// Check if line/s adjecent to unique identical pairs are the same. 190 | /// This is to detect a block of changes. The detection moves from top to bottom. 191 | /// i.e consider one/many pairs adjecent to found pair. 192 | for (index, lookup) in nas.enumerated() { 193 | if case let .lineNumber(oldLine) = lookup { 194 | let incrIndex = index + 1 195 | let incrOldLine = oldLine + 1 196 | if incrIndex < nas.count, incrOldLine < oas.count, oas[incrOldLine].pointsToSameSymEntry(as: nas[incrIndex]) { 197 | nas[incrIndex] = .lineNumber(incrOldLine) 198 | oas[incrOldLine] = .lineNumber(incrIndex) 199 | } 200 | } 201 | } 202 | 203 | /// Pass 5 204 | /// Same as pass 4 but looks before the unique identical pair 205 | /// to find blocks. 206 | for (index, lookup) in nas.enumerated().reversed() { 207 | if case let .lineNumber(oldLine) = lookup { 208 | let decrIndex = index - 1 209 | let decrOldLine = oldLine - 1 210 | if decrIndex >= 0, decrOldLine >= 0, oas[decrOldLine].pointsToSameSymEntry(as: nas[decrIndex]) { 211 | nas[decrIndex] = .lineNumber(decrOldLine) 212 | oas[decrOldLine] = .lineNumber(decrIndex) 213 | } 214 | } 215 | } 216 | 217 | /// Pass 6 218 | /// Go through oas and nas and collect change set 219 | /// old: a b c d 220 | /// new: e a b d f 221 | /// change: [insert e at 0, insert f at 4] [delete c from 2] 222 | var operations = [DiffOperation]() 223 | var deletionKeeper = [Int: Int]() // lineNum: how many lines deleted prior to this 224 | var runningOffset = 0 225 | for (index, item) in oas.enumerated() { 226 | if case .sym(_) = item { 227 | operations.append(.delete(oldContent[index], index)) 228 | runningOffset += 1 229 | } 230 | deletionKeeper[index] = runningOffset 231 | } 232 | 233 | runningOffset = 0 234 | for (index, item) in nas.enumerated() { 235 | switch item { 236 | case .sym(_): 237 | operations.append(.add(newContent[index], index)) 238 | runningOffset += 1 239 | case let .lineNumber(oldLineNumber): 240 | /// Maybe the object hash is the same but the equality is not 241 | /// good point for getting internal diff 242 | if newContent[index] != oldContent[oldLineNumber] { 243 | operations.append(.update(oldContent[oldLineNumber], newContent[index], index)) 244 | } 245 | let deleteOffSetToNegect = deletionKeeper[oldLineNumber] ?? 0 246 | let calculatedIndexAfterPreviousInsertionAndDeletionCounts = oldLineNumber - deleteOffSetToNegect + runningOffset 247 | if calculatedIndexAfterPreviousInsertionAndDeletionCounts != index { 248 | operations.append(.move(newContent[index], oldLineNumber, index)) 249 | } 250 | } 251 | } 252 | 253 | return operations 254 | } 255 | 256 | /** Limitation: Can't extend a protocol with a generic typed enum (generic type in general) 257 | extension Array where Element: Operation { } 258 | */ 259 | public func orderedOperation(from operations: [DiffOperation]) -> [DiffOperation.Simple] { 260 | /// Deletions need to happen from higher index to lower (to avoid corrupted indexes) 261 | /// [x, y, z] will be corrupt if we attempt [d(0), d(2), d(1)] 262 | /// d(0) succeeds then array is [x,y]. Attempting to delete at index 2 produces out of bounds error. 263 | /// Therefore we sort in descending order of index 264 | var deletions = [Int: DiffOperation.Simple]() 265 | var insertions = [DiffOperation.Simple]() 266 | var updates = [DiffOperation.Simple]() 267 | 268 | for oper in operations { 269 | switch oper { 270 | case let .update(item, newItem, index): 271 | updates.append(.update(item, newItem, index)) 272 | case let .add(item, atIndex): 273 | insertions.append(.add(item, atIndex)) 274 | case let .delete(item, from): 275 | deletions[from] = .delete(item, from) 276 | case let .move(item, from, to): 277 | insertions.append(.add(item, to)) 278 | deletions[from] = .delete(item, from) 279 | } 280 | } 281 | let descendingOrderedIndexDeletions = deletions.sorted(by: {$0.0 > $1.0 }).map{ $0.1 } 282 | return descendingOrderedIndexDeletions + insertions + updates 283 | } 284 | 285 | 286 | extension Array where Element: Hashable { 287 | 288 | public func merged(with operations: [DiffOperation]) -> Array { 289 | let orderedOperations = orderedOperation(from: operations) 290 | return self.merged(with: orderedOperations) 291 | } 292 | 293 | public func merged(with operations: [DiffOperation.Simple]) -> Array { 294 | /// might not work on collection as we need to initialize a concrete type 295 | var mutableCollection: [Element] = self 296 | for operation in operations { 297 | switch operation { 298 | case let .add(item, addAt): 299 | mutableCollection.insert(item, at: addAt) 300 | case let .update(oldItem, newItem, updateAt): 301 | assert(mutableCollection[updateAt] == oldItem, "update doesnot have proper old value") 302 | mutableCollection[updateAt] = newItem 303 | case let .delete(_, atIndex): 304 | mutableCollection.remove(at: atIndex) 305 | } 306 | } 307 | return mutableCollection 308 | } 309 | 310 | } 311 | -------------------------------------------------------------------------------- /FastDiff.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXAggregateTarget section */ 10 | "FastDiff::FastDiffPackageTests::ProductTarget" /* FastDiffPackageTests */ = { 11 | isa = PBXAggregateTarget; 12 | buildConfigurationList = OBJ_65 /* Build configuration list for PBXAggregateTarget "FastDiffPackageTests" */; 13 | buildPhases = ( 14 | ); 15 | dependencies = ( 16 | OBJ_68 /* PBXTargetDependency */, 17 | ); 18 | name = FastDiffPackageTests; 19 | productName = FastDiffPackageTests; 20 | }; 21 | /* End PBXAggregateTarget section */ 22 | 23 | /* Begin PBXBuildFile section */ 24 | 284009BA25D6DDAD009EB9E4 /* FastDiffDiffAllLevelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 284009B925D6DDAD009EB9E4 /* FastDiffDiffAllLevelTests.swift */; }; 25 | 284009C325D6DF6A009EB9E4 /* AlgoChecker in Frameworks */ = {isa = PBXBuildFile; productRef = 284009C225D6DF6A009EB9E4 /* AlgoChecker */; }; 26 | 284009CC25D6DF93009EB9E4 /* AlgoChecker in Frameworks */ = {isa = PBXBuildFile; productRef = 284009CB25D6DF93009EB9E4 /* AlgoChecker */; }; 27 | 284009D525D6E07C009EB9E4 /* Randomizer in Frameworks */ = {isa = PBXBuildFile; productRef = 284009D425D6E07C009EB9E4 /* Randomizer */; }; 28 | 284009DD25D6E08C009EB9E4 /* Randomizer in Frameworks */ = {isa = PBXBuildFile; productRef = 284009DC25D6E08C009EB9E4 /* Randomizer */; }; 29 | OBJ_54 /* Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* Diffable.swift */; }; 30 | OBJ_55 /* DiffingAlgorithm.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_10 /* DiffingAlgorithm.swift */; }; 31 | OBJ_56 /* InternalDiff.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* InternalDiff.swift */; }; 32 | OBJ_63 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; }; 33 | OBJ_74 /* FastDiffComplexityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_14 /* FastDiffComplexityTests.swift */; }; 34 | OBJ_75 /* FastDiffTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_15 /* FastDiffTests.swift */; }; 35 | OBJ_76 /* XCTestManifests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_16 /* XCTestManifests.swift */; }; 36 | OBJ_79 /* FastDiff.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "FastDiff::FastDiff::Product" /* FastDiff.framework */; }; 37 | /* End PBXBuildFile section */ 38 | 39 | /* Begin PBXContainerItemProxy section */ 40 | 284009B025D6DD8D009EB9E4 /* PBXContainerItemProxy */ = { 41 | isa = PBXContainerItemProxy; 42 | containerPortal = OBJ_1 /* Project object */; 43 | proxyType = 1; 44 | remoteGlobalIDString = "AlgoChecker::AlgoChecker"; 45 | remoteInfo = AlgoChecker; 46 | }; 47 | 284009B125D6DD8D009EB9E4 /* PBXContainerItemProxy */ = { 48 | isa = PBXContainerItemProxy; 49 | containerPortal = OBJ_1 /* Project object */; 50 | proxyType = 1; 51 | remoteGlobalIDString = "FastDiff::FastDiff"; 52 | remoteInfo = FastDiff; 53 | }; 54 | 284009B625D6DD8E009EB9E4 /* PBXContainerItemProxy */ = { 55 | isa = PBXContainerItemProxy; 56 | containerPortal = OBJ_1 /* Project object */; 57 | proxyType = 1; 58 | remoteGlobalIDString = "FastDiff::FastDiffTests"; 59 | remoteInfo = FastDiffTests; 60 | }; 61 | /* End PBXContainerItemProxy section */ 62 | 63 | /* Begin PBXFileReference section */ 64 | 284009B925D6DDAD009EB9E4 /* FastDiffDiffAllLevelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FastDiffDiffAllLevelTests.swift; sourceTree = ""; }; 65 | "AlgoChecker::AlgoChecker::Product" /* AlgoChecker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AlgoChecker.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 66 | "FastDiff::FastDiff::Product" /* FastDiff.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FastDiff.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 67 | "FastDiff::FastDiffTests::Product" /* FastDiffTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; path = FastDiffTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 68 | OBJ_10 /* DiffingAlgorithm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiffingAlgorithm.swift; sourceTree = ""; }; 69 | OBJ_11 /* InternalDiff.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternalDiff.swift; sourceTree = ""; }; 70 | OBJ_14 /* FastDiffComplexityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FastDiffComplexityTests.swift; sourceTree = ""; }; 71 | OBJ_15 /* FastDiffTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FastDiffTests.swift; sourceTree = ""; }; 72 | OBJ_16 /* XCTestManifests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTestManifests.swift; sourceTree = ""; }; 73 | OBJ_28 /* Documentation */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Documentation; sourceTree = SOURCE_ROOT; }; 74 | OBJ_29 /* FastDiff */ = {isa = PBXFileReference; lastKnownFileType = folder; path = FastDiff; sourceTree = SOURCE_ROOT; }; 75 | OBJ_30 /* LICENSE.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = ""; }; 76 | OBJ_31 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 77 | OBJ_32 /* logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = logo.png; sourceTree = ""; }; 78 | OBJ_33 /* FastDiff.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = FastDiff.podspec; sourceTree = ""; }; 79 | OBJ_6 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; 80 | OBJ_9 /* Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Diffable.swift; sourceTree = ""; }; 81 | /* End PBXFileReference section */ 82 | 83 | /* Begin PBXFrameworksBuildPhase section */ 84 | OBJ_42 /* Frameworks */ = { 85 | isa = PBXFrameworksBuildPhase; 86 | buildActionMask = 0; 87 | files = ( 88 | 284009C325D6DF6A009EB9E4 /* AlgoChecker in Frameworks */, 89 | 284009D525D6E07C009EB9E4 /* Randomizer in Frameworks */, 90 | ); 91 | runOnlyForDeploymentPostprocessing = 0; 92 | }; 93 | OBJ_57 /* Frameworks */ = { 94 | isa = PBXFrameworksBuildPhase; 95 | buildActionMask = 0; 96 | files = ( 97 | ); 98 | runOnlyForDeploymentPostprocessing = 0; 99 | }; 100 | OBJ_77 /* Frameworks */ = { 101 | isa = PBXFrameworksBuildPhase; 102 | buildActionMask = 0; 103 | files = ( 104 | OBJ_79 /* FastDiff.framework in Frameworks */, 105 | 284009CC25D6DF93009EB9E4 /* AlgoChecker in Frameworks */, 106 | 284009DD25D6E08C009EB9E4 /* Randomizer in Frameworks */, 107 | ); 108 | runOnlyForDeploymentPostprocessing = 0; 109 | }; 110 | /* End PBXFrameworksBuildPhase section */ 111 | 112 | /* Begin PBXGroup section */ 113 | 284009CA25D6DF93009EB9E4 /* Frameworks */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | ); 117 | name = Frameworks; 118 | sourceTree = ""; 119 | }; 120 | OBJ_12 /* Tests */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | OBJ_13 /* FastDiffTests */, 124 | ); 125 | name = Tests; 126 | sourceTree = SOURCE_ROOT; 127 | }; 128 | OBJ_13 /* FastDiffTests */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | OBJ_14 /* FastDiffComplexityTests.swift */, 132 | OBJ_15 /* FastDiffTests.swift */, 133 | OBJ_16 /* XCTestManifests.swift */, 134 | 284009B925D6DDAD009EB9E4 /* FastDiffDiffAllLevelTests.swift */, 135 | ); 136 | name = FastDiffTests; 137 | path = Tests/FastDiffTests; 138 | sourceTree = SOURCE_ROOT; 139 | }; 140 | OBJ_24 /* Products */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | "FastDiff::FastDiffTests::Product" /* FastDiffTests.xctest */, 144 | "FastDiff::FastDiff::Product" /* FastDiff.framework */, 145 | "AlgoChecker::AlgoChecker::Product" /* AlgoChecker.framework */, 146 | ); 147 | name = Products; 148 | sourceTree = BUILT_PRODUCTS_DIR; 149 | }; 150 | OBJ_5 = { 151 | isa = PBXGroup; 152 | children = ( 153 | OBJ_6 /* Package.swift */, 154 | OBJ_7 /* Sources */, 155 | OBJ_12 /* Tests */, 156 | OBJ_24 /* Products */, 157 | OBJ_28 /* Documentation */, 158 | OBJ_29 /* FastDiff */, 159 | OBJ_30 /* LICENSE.md */, 160 | OBJ_31 /* README.md */, 161 | OBJ_32 /* logo.png */, 162 | OBJ_33 /* FastDiff.podspec */, 163 | 284009CA25D6DF93009EB9E4 /* Frameworks */, 164 | ); 165 | sourceTree = ""; 166 | }; 167 | OBJ_7 /* Sources */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | OBJ_8 /* FastDiff */, 171 | ); 172 | name = Sources; 173 | sourceTree = SOURCE_ROOT; 174 | }; 175 | OBJ_8 /* FastDiff */ = { 176 | isa = PBXGroup; 177 | children = ( 178 | OBJ_9 /* Diffable.swift */, 179 | OBJ_10 /* DiffingAlgorithm.swift */, 180 | OBJ_11 /* InternalDiff.swift */, 181 | ); 182 | name = FastDiff; 183 | path = Sources/FastDiff; 184 | sourceTree = SOURCE_ROOT; 185 | }; 186 | /* End PBXGroup section */ 187 | 188 | /* Begin PBXNativeTarget section */ 189 | "AlgoChecker::AlgoChecker" /* AlgoChecker */ = { 190 | isa = PBXNativeTarget; 191 | buildConfigurationList = OBJ_35 /* Build configuration list for PBXNativeTarget "AlgoChecker" */; 192 | buildPhases = ( 193 | OBJ_38 /* Sources */, 194 | OBJ_42 /* Frameworks */, 195 | ); 196 | buildRules = ( 197 | ); 198 | dependencies = ( 199 | ); 200 | name = AlgoChecker; 201 | packageProductDependencies = ( 202 | 284009C225D6DF6A009EB9E4 /* AlgoChecker */, 203 | 284009D425D6E07C009EB9E4 /* Randomizer */, 204 | ); 205 | productName = AlgoChecker; 206 | productReference = "AlgoChecker::AlgoChecker::Product" /* AlgoChecker.framework */; 207 | productType = "com.apple.product-type.framework"; 208 | }; 209 | "AlgoChecker::SwiftPMPackageDescription" /* AlgoCheckerPackageDescription */ = { 210 | isa = PBXNativeTarget; 211 | buildConfigurationList = OBJ_44 /* Build configuration list for PBXNativeTarget "AlgoCheckerPackageDescription" */; 212 | buildPhases = ( 213 | OBJ_47 /* Sources */, 214 | ); 215 | buildRules = ( 216 | ); 217 | dependencies = ( 218 | ); 219 | name = AlgoCheckerPackageDescription; 220 | productName = AlgoCheckerPackageDescription; 221 | productType = "com.apple.product-type.framework"; 222 | }; 223 | "FastDiff::FastDiff" /* FastDiff */ = { 224 | isa = PBXNativeTarget; 225 | buildConfigurationList = OBJ_50 /* Build configuration list for PBXNativeTarget "FastDiff" */; 226 | buildPhases = ( 227 | OBJ_53 /* Sources */, 228 | OBJ_57 /* Frameworks */, 229 | ); 230 | buildRules = ( 231 | ); 232 | dependencies = ( 233 | ); 234 | name = FastDiff; 235 | productName = FastDiff; 236 | productReference = "FastDiff::FastDiff::Product" /* FastDiff.framework */; 237 | productType = "com.apple.product-type.framework"; 238 | }; 239 | "FastDiff::FastDiffTests" /* FastDiffTests */ = { 240 | isa = PBXNativeTarget; 241 | buildConfigurationList = OBJ_70 /* Build configuration list for PBXNativeTarget "FastDiffTests" */; 242 | buildPhases = ( 243 | OBJ_73 /* Sources */, 244 | OBJ_77 /* Frameworks */, 245 | ); 246 | buildRules = ( 247 | ); 248 | dependencies = ( 249 | OBJ_80 /* PBXTargetDependency */, 250 | OBJ_81 /* PBXTargetDependency */, 251 | ); 252 | name = FastDiffTests; 253 | packageProductDependencies = ( 254 | 284009CB25D6DF93009EB9E4 /* AlgoChecker */, 255 | 284009DC25D6E08C009EB9E4 /* Randomizer */, 256 | ); 257 | productName = FastDiffTests; 258 | productReference = "FastDiff::FastDiffTests::Product" /* FastDiffTests.xctest */; 259 | productType = "com.apple.product-type.bundle.unit-test"; 260 | }; 261 | "FastDiff::SwiftPMPackageDescription" /* FastDiffPackageDescription */ = { 262 | isa = PBXNativeTarget; 263 | buildConfigurationList = OBJ_59 /* Build configuration list for PBXNativeTarget "FastDiffPackageDescription" */; 264 | buildPhases = ( 265 | OBJ_62 /* Sources */, 266 | ); 267 | buildRules = ( 268 | ); 269 | dependencies = ( 270 | ); 271 | name = FastDiffPackageDescription; 272 | productName = FastDiffPackageDescription; 273 | productType = "com.apple.product-type.framework"; 274 | }; 275 | /* End PBXNativeTarget section */ 276 | 277 | /* Begin PBXProject section */ 278 | OBJ_1 /* Project object */ = { 279 | isa = PBXProject; 280 | attributes = { 281 | LastSwiftMigration = 9999; 282 | LastUpgradeCheck = 9999; 283 | }; 284 | buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "FastDiff" */; 285 | compatibilityVersion = "Xcode 3.2"; 286 | developmentRegion = en; 287 | hasScannedForEncodings = 0; 288 | knownRegions = ( 289 | en, 290 | ); 291 | mainGroup = OBJ_5; 292 | packageReferences = ( 293 | 284009C125D6DF6A009EB9E4 /* XCRemoteSwiftPackageReference "AlgorithmChecker" */, 294 | 284009D325D6E07C009EB9E4 /* XCRemoteSwiftPackageReference "Randomizer" */, 295 | ); 296 | productRefGroup = OBJ_24 /* Products */; 297 | projectDirPath = ""; 298 | projectRoot = ""; 299 | targets = ( 300 | "AlgoChecker::AlgoChecker" /* AlgoChecker */, 301 | "AlgoChecker::SwiftPMPackageDescription" /* AlgoCheckerPackageDescription */, 302 | "FastDiff::FastDiff" /* FastDiff */, 303 | "FastDiff::SwiftPMPackageDescription" /* FastDiffPackageDescription */, 304 | "FastDiff::FastDiffPackageTests::ProductTarget" /* FastDiffPackageTests */, 305 | "FastDiff::FastDiffTests" /* FastDiffTests */, 306 | ); 307 | }; 308 | /* End PBXProject section */ 309 | 310 | /* Begin PBXSourcesBuildPhase section */ 311 | OBJ_38 /* Sources */ = { 312 | isa = PBXSourcesBuildPhase; 313 | buildActionMask = 0; 314 | files = ( 315 | ); 316 | runOnlyForDeploymentPostprocessing = 0; 317 | }; 318 | OBJ_47 /* Sources */ = { 319 | isa = PBXSourcesBuildPhase; 320 | buildActionMask = 0; 321 | files = ( 322 | ); 323 | runOnlyForDeploymentPostprocessing = 0; 324 | }; 325 | OBJ_53 /* Sources */ = { 326 | isa = PBXSourcesBuildPhase; 327 | buildActionMask = 0; 328 | files = ( 329 | OBJ_54 /* Diffable.swift in Sources */, 330 | OBJ_55 /* DiffingAlgorithm.swift in Sources */, 331 | OBJ_56 /* InternalDiff.swift in Sources */, 332 | ); 333 | runOnlyForDeploymentPostprocessing = 0; 334 | }; 335 | OBJ_62 /* Sources */ = { 336 | isa = PBXSourcesBuildPhase; 337 | buildActionMask = 0; 338 | files = ( 339 | OBJ_63 /* Package.swift in Sources */, 340 | ); 341 | runOnlyForDeploymentPostprocessing = 0; 342 | }; 343 | OBJ_73 /* Sources */ = { 344 | isa = PBXSourcesBuildPhase; 345 | buildActionMask = 0; 346 | files = ( 347 | OBJ_74 /* FastDiffComplexityTests.swift in Sources */, 348 | OBJ_75 /* FastDiffTests.swift in Sources */, 349 | 284009BA25D6DDAD009EB9E4 /* FastDiffDiffAllLevelTests.swift in Sources */, 350 | OBJ_76 /* XCTestManifests.swift in Sources */, 351 | ); 352 | runOnlyForDeploymentPostprocessing = 0; 353 | }; 354 | /* End PBXSourcesBuildPhase section */ 355 | 356 | /* Begin PBXTargetDependency section */ 357 | OBJ_68 /* PBXTargetDependency */ = { 358 | isa = PBXTargetDependency; 359 | target = "FastDiff::FastDiffTests" /* FastDiffTests */; 360 | targetProxy = 284009B625D6DD8E009EB9E4 /* PBXContainerItemProxy */; 361 | }; 362 | OBJ_80 /* PBXTargetDependency */ = { 363 | isa = PBXTargetDependency; 364 | target = "AlgoChecker::AlgoChecker" /* AlgoChecker */; 365 | targetProxy = 284009B025D6DD8D009EB9E4 /* PBXContainerItemProxy */; 366 | }; 367 | OBJ_81 /* PBXTargetDependency */ = { 368 | isa = PBXTargetDependency; 369 | target = "FastDiff::FastDiff" /* FastDiff */; 370 | targetProxy = 284009B125D6DD8D009EB9E4 /* PBXContainerItemProxy */; 371 | }; 372 | /* End PBXTargetDependency section */ 373 | 374 | /* Begin XCBuildConfiguration section */ 375 | OBJ_3 /* Debug */ = { 376 | isa = XCBuildConfiguration; 377 | buildSettings = { 378 | CLANG_ENABLE_OBJC_ARC = YES; 379 | COMBINE_HIDPI_IMAGES = YES; 380 | COPY_PHASE_STRIP = NO; 381 | CURRENT_PROJECT_VERSION = 1; 382 | DEBUG_INFORMATION_FORMAT = dwarf; 383 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 384 | ENABLE_NS_ASSERTIONS = YES; 385 | GCC_OPTIMIZATION_LEVEL = 0; 386 | GCC_PREPROCESSOR_DEFINITIONS = ( 387 | "$(inherited)", 388 | "SWIFT_PACKAGE=1", 389 | "DEBUG=1", 390 | ); 391 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 392 | MACOSX_DEPLOYMENT_TARGET = 10.10; 393 | ONLY_ACTIVE_ARCH = YES; 394 | OTHER_SWIFT_FLAGS = "$(inherited) -DXcode"; 395 | PRODUCT_NAME = "$(TARGET_NAME)"; 396 | SDKROOT = iphoneos; 397 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; 398 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE DEBUG"; 399 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 400 | USE_HEADERMAP = NO; 401 | }; 402 | name = Debug; 403 | }; 404 | OBJ_36 /* Debug */ = { 405 | isa = XCBuildConfiguration; 406 | buildSettings = { 407 | ENABLE_TESTABILITY = YES; 408 | FRAMEWORK_SEARCH_PATHS = ( 409 | "$(inherited)", 410 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 411 | ); 412 | HEADER_SEARCH_PATHS = "$(inherited)"; 413 | INFOPLIST_FILE = FastDiff.xcodeproj/AlgoChecker_Info.plist; 414 | LD_RUNPATH_SEARCH_PATHS = ( 415 | "$(inherited)", 416 | "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx", 417 | ); 418 | OTHER_CFLAGS = "$(inherited)"; 419 | OTHER_LDFLAGS = "$(inherited)"; 420 | OTHER_SWIFT_FLAGS = "$(inherited)"; 421 | PRODUCT_BUNDLE_IDENTIFIER = AlgoChecker; 422 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 423 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 424 | SKIP_INSTALL = YES; 425 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; 426 | SWIFT_VERSION = 4.2; 427 | TARGET_NAME = AlgoChecker; 428 | }; 429 | name = Debug; 430 | }; 431 | OBJ_37 /* Release */ = { 432 | isa = XCBuildConfiguration; 433 | buildSettings = { 434 | ENABLE_TESTABILITY = YES; 435 | FRAMEWORK_SEARCH_PATHS = ( 436 | "$(inherited)", 437 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 438 | ); 439 | HEADER_SEARCH_PATHS = "$(inherited)"; 440 | INFOPLIST_FILE = FastDiff.xcodeproj/AlgoChecker_Info.plist; 441 | LD_RUNPATH_SEARCH_PATHS = ( 442 | "$(inherited)", 443 | "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx", 444 | ); 445 | OTHER_CFLAGS = "$(inherited)"; 446 | OTHER_LDFLAGS = "$(inherited)"; 447 | OTHER_SWIFT_FLAGS = "$(inherited)"; 448 | PRODUCT_BUNDLE_IDENTIFIER = AlgoChecker; 449 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 450 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 451 | SKIP_INSTALL = YES; 452 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; 453 | SWIFT_VERSION = 4.2; 454 | TARGET_NAME = AlgoChecker; 455 | }; 456 | name = Release; 457 | }; 458 | OBJ_4 /* Release */ = { 459 | isa = XCBuildConfiguration; 460 | buildSettings = { 461 | CLANG_ENABLE_OBJC_ARC = YES; 462 | COMBINE_HIDPI_IMAGES = YES; 463 | COPY_PHASE_STRIP = YES; 464 | CURRENT_PROJECT_VERSION = 1; 465 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 466 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 467 | GCC_OPTIMIZATION_LEVEL = s; 468 | GCC_PREPROCESSOR_DEFINITIONS = ( 469 | "$(inherited)", 470 | "SWIFT_PACKAGE=1", 471 | ); 472 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 473 | MACOSX_DEPLOYMENT_TARGET = 10.10; 474 | OTHER_SWIFT_FLAGS = "$(inherited) -DXcode"; 475 | PRODUCT_NAME = "$(TARGET_NAME)"; 476 | SDKROOT = iphoneos; 477 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; 478 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE"; 479 | SWIFT_COMPILATION_MODE = wholemodule; 480 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 481 | USE_HEADERMAP = NO; 482 | }; 483 | name = Release; 484 | }; 485 | OBJ_45 /* Debug */ = { 486 | isa = XCBuildConfiguration; 487 | buildSettings = { 488 | LD = /usr/bin/true; 489 | OTHER_SWIFT_FLAGS = "-swift-version 4.2 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk -package-description-version 4.2.0"; 490 | SWIFT_VERSION = 4.2; 491 | }; 492 | name = Debug; 493 | }; 494 | OBJ_46 /* Release */ = { 495 | isa = XCBuildConfiguration; 496 | buildSettings = { 497 | LD = /usr/bin/true; 498 | OTHER_SWIFT_FLAGS = "-swift-version 4.2 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk -package-description-version 4.2.0"; 499 | SWIFT_VERSION = 4.2; 500 | }; 501 | name = Release; 502 | }; 503 | OBJ_51 /* Debug */ = { 504 | isa = XCBuildConfiguration; 505 | buildSettings = { 506 | ENABLE_TESTABILITY = YES; 507 | FRAMEWORK_SEARCH_PATHS = ( 508 | "$(inherited)", 509 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 510 | ); 511 | HEADER_SEARCH_PATHS = "$(inherited)"; 512 | INFOPLIST_FILE = FastDiff.xcodeproj/FastDiff_Info.plist; 513 | LD_RUNPATH_SEARCH_PATHS = ( 514 | "$(inherited)", 515 | "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx", 516 | ); 517 | OTHER_CFLAGS = "$(inherited)"; 518 | OTHER_LDFLAGS = "$(inherited)"; 519 | OTHER_SWIFT_FLAGS = "$(inherited)"; 520 | PRODUCT_BUNDLE_IDENTIFIER = FastDiff; 521 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 522 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 523 | SKIP_INSTALL = YES; 524 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; 525 | SWIFT_VERSION = 4.2; 526 | TARGET_NAME = FastDiff; 527 | }; 528 | name = Debug; 529 | }; 530 | OBJ_52 /* Release */ = { 531 | isa = XCBuildConfiguration; 532 | buildSettings = { 533 | ENABLE_TESTABILITY = YES; 534 | FRAMEWORK_SEARCH_PATHS = ( 535 | "$(inherited)", 536 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 537 | ); 538 | HEADER_SEARCH_PATHS = "$(inherited)"; 539 | INFOPLIST_FILE = FastDiff.xcodeproj/FastDiff_Info.plist; 540 | LD_RUNPATH_SEARCH_PATHS = ( 541 | "$(inherited)", 542 | "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx", 543 | ); 544 | OTHER_CFLAGS = "$(inherited)"; 545 | OTHER_LDFLAGS = "$(inherited)"; 546 | OTHER_SWIFT_FLAGS = "$(inherited)"; 547 | PRODUCT_BUNDLE_IDENTIFIER = FastDiff; 548 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 549 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 550 | SKIP_INSTALL = YES; 551 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; 552 | SWIFT_VERSION = 4.2; 553 | TARGET_NAME = FastDiff; 554 | }; 555 | name = Release; 556 | }; 557 | OBJ_60 /* Debug */ = { 558 | isa = XCBuildConfiguration; 559 | buildSettings = { 560 | LD = /usr/bin/true; 561 | OTHER_SWIFT_FLAGS = "-swift-version 4.2 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk -package-description-version 4.2.0"; 562 | SWIFT_VERSION = 4.2; 563 | }; 564 | name = Debug; 565 | }; 566 | OBJ_61 /* Release */ = { 567 | isa = XCBuildConfiguration; 568 | buildSettings = { 569 | LD = /usr/bin/true; 570 | OTHER_SWIFT_FLAGS = "-swift-version 4.2 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk -package-description-version 4.2.0"; 571 | SWIFT_VERSION = 4.2; 572 | }; 573 | name = Release; 574 | }; 575 | OBJ_66 /* Debug */ = { 576 | isa = XCBuildConfiguration; 577 | buildSettings = { 578 | }; 579 | name = Debug; 580 | }; 581 | OBJ_67 /* Release */ = { 582 | isa = XCBuildConfiguration; 583 | buildSettings = { 584 | }; 585 | name = Release; 586 | }; 587 | OBJ_71 /* Debug */ = { 588 | isa = XCBuildConfiguration; 589 | buildSettings = { 590 | CLANG_ENABLE_MODULES = YES; 591 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 592 | FRAMEWORK_SEARCH_PATHS = ( 593 | "$(inherited)", 594 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 595 | ); 596 | HEADER_SEARCH_PATHS = "$(inherited)"; 597 | INFOPLIST_FILE = FastDiff.xcodeproj/FastDiffTests_Info.plist; 598 | LD_RUNPATH_SEARCH_PATHS = ( 599 | "$(inherited)", 600 | "@loader_path/../Frameworks", 601 | "@loader_path/Frameworks", 602 | ); 603 | OTHER_CFLAGS = "$(inherited)"; 604 | OTHER_LDFLAGS = "$(inherited)"; 605 | OTHER_SWIFT_FLAGS = "$(inherited)"; 606 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; 607 | SWIFT_VERSION = 4.2; 608 | TARGET_NAME = FastDiffTests; 609 | }; 610 | name = Debug; 611 | }; 612 | OBJ_72 /* Release */ = { 613 | isa = XCBuildConfiguration; 614 | buildSettings = { 615 | CLANG_ENABLE_MODULES = YES; 616 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 617 | FRAMEWORK_SEARCH_PATHS = ( 618 | "$(inherited)", 619 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 620 | ); 621 | HEADER_SEARCH_PATHS = "$(inherited)"; 622 | INFOPLIST_FILE = FastDiff.xcodeproj/FastDiffTests_Info.plist; 623 | LD_RUNPATH_SEARCH_PATHS = ( 624 | "$(inherited)", 625 | "@loader_path/../Frameworks", 626 | "@loader_path/Frameworks", 627 | ); 628 | OTHER_CFLAGS = "$(inherited)"; 629 | OTHER_LDFLAGS = "$(inherited)"; 630 | OTHER_SWIFT_FLAGS = "$(inherited)"; 631 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; 632 | SWIFT_VERSION = 4.2; 633 | TARGET_NAME = FastDiffTests; 634 | }; 635 | name = Release; 636 | }; 637 | /* End XCBuildConfiguration section */ 638 | 639 | /* Begin XCConfigurationList section */ 640 | OBJ_2 /* Build configuration list for PBXProject "FastDiff" */ = { 641 | isa = XCConfigurationList; 642 | buildConfigurations = ( 643 | OBJ_3 /* Debug */, 644 | OBJ_4 /* Release */, 645 | ); 646 | defaultConfigurationIsVisible = 0; 647 | defaultConfigurationName = Release; 648 | }; 649 | OBJ_35 /* Build configuration list for PBXNativeTarget "AlgoChecker" */ = { 650 | isa = XCConfigurationList; 651 | buildConfigurations = ( 652 | OBJ_36 /* Debug */, 653 | OBJ_37 /* Release */, 654 | ); 655 | defaultConfigurationIsVisible = 0; 656 | defaultConfigurationName = Release; 657 | }; 658 | OBJ_44 /* Build configuration list for PBXNativeTarget "AlgoCheckerPackageDescription" */ = { 659 | isa = XCConfigurationList; 660 | buildConfigurations = ( 661 | OBJ_45 /* Debug */, 662 | OBJ_46 /* Release */, 663 | ); 664 | defaultConfigurationIsVisible = 0; 665 | defaultConfigurationName = Release; 666 | }; 667 | OBJ_50 /* Build configuration list for PBXNativeTarget "FastDiff" */ = { 668 | isa = XCConfigurationList; 669 | buildConfigurations = ( 670 | OBJ_51 /* Debug */, 671 | OBJ_52 /* Release */, 672 | ); 673 | defaultConfigurationIsVisible = 0; 674 | defaultConfigurationName = Release; 675 | }; 676 | OBJ_59 /* Build configuration list for PBXNativeTarget "FastDiffPackageDescription" */ = { 677 | isa = XCConfigurationList; 678 | buildConfigurations = ( 679 | OBJ_60 /* Debug */, 680 | OBJ_61 /* Release */, 681 | ); 682 | defaultConfigurationIsVisible = 0; 683 | defaultConfigurationName = Release; 684 | }; 685 | OBJ_65 /* Build configuration list for PBXAggregateTarget "FastDiffPackageTests" */ = { 686 | isa = XCConfigurationList; 687 | buildConfigurations = ( 688 | OBJ_66 /* Debug */, 689 | OBJ_67 /* Release */, 690 | ); 691 | defaultConfigurationIsVisible = 0; 692 | defaultConfigurationName = Release; 693 | }; 694 | OBJ_70 /* Build configuration list for PBXNativeTarget "FastDiffTests" */ = { 695 | isa = XCConfigurationList; 696 | buildConfigurations = ( 697 | OBJ_71 /* Debug */, 698 | OBJ_72 /* Release */, 699 | ); 700 | defaultConfigurationIsVisible = 0; 701 | defaultConfigurationName = Release; 702 | }; 703 | /* End XCConfigurationList section */ 704 | 705 | /* Begin XCRemoteSwiftPackageReference section */ 706 | 284009C125D6DF6A009EB9E4 /* XCRemoteSwiftPackageReference "AlgorithmChecker" */ = { 707 | isa = XCRemoteSwiftPackageReference; 708 | repositoryURL = "https://github.com/kandelvijaya/AlgorithmChecker.git"; 709 | requirement = { 710 | kind = upToNextMajorVersion; 711 | minimumVersion = 0.1.5; 712 | }; 713 | }; 714 | 284009D325D6E07C009EB9E4 /* XCRemoteSwiftPackageReference "Randomizer" */ = { 715 | isa = XCRemoteSwiftPackageReference; 716 | repositoryURL = "https://github.com/kandelvijaya/Randomizer.git"; 717 | requirement = { 718 | branch = master; 719 | kind = branch; 720 | }; 721 | }; 722 | /* End XCRemoteSwiftPackageReference section */ 723 | 724 | /* Begin XCSwiftPackageProductDependency section */ 725 | 284009C225D6DF6A009EB9E4 /* AlgoChecker */ = { 726 | isa = XCSwiftPackageProductDependency; 727 | package = 284009C125D6DF6A009EB9E4 /* XCRemoteSwiftPackageReference "AlgorithmChecker" */; 728 | productName = AlgoChecker; 729 | }; 730 | 284009CB25D6DF93009EB9E4 /* AlgoChecker */ = { 731 | isa = XCSwiftPackageProductDependency; 732 | package = 284009C125D6DF6A009EB9E4 /* XCRemoteSwiftPackageReference "AlgorithmChecker" */; 733 | productName = AlgoChecker; 734 | }; 735 | 284009D425D6E07C009EB9E4 /* Randomizer */ = { 736 | isa = XCSwiftPackageProductDependency; 737 | package = 284009D325D6E07C009EB9E4 /* XCRemoteSwiftPackageReference "Randomizer" */; 738 | productName = Randomizer; 739 | }; 740 | 284009DC25D6E08C009EB9E4 /* Randomizer */ = { 741 | isa = XCSwiftPackageProductDependency; 742 | package = 284009D325D6E07C009EB9E4 /* XCRemoteSwiftPackageReference "Randomizer" */; 743 | productName = Randomizer; 744 | }; 745 | /* End XCSwiftPackageProductDependency section */ 746 | }; 747 | rootObject = OBJ_1 /* Project object */; 748 | } 749 | --------------------------------------------------------------------------------