├── .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 
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 |
--------------------------------------------------------------------------------