🦀Amazingly incredible extraordinary lightning fast diffing in Swift

Overview

DeepDiff

❤️ Support my apps ❤️

❤️ ❤️ 😇 😍 🤘 ❤️ ❤️

CI Status Version Carthage Compatible License Platform Swift

DeepDiff tells the difference between 2 collections and the changes as edit steps. It also supports Texture, see Texture example

Usage

Basic

The result of diff is an array of changes, which is [Change]. A Change can be

  • .insert: The item was inserted at an index
  • .delete: The item was deleted from an index
  • .replace: The item at this index was replaced by another item
  • .move: The same item has moved from this index to another index

By default, there is no .move. But since .move is just .delete followed by .insert of the same item, it can be reduced by specifying reduceMove to true.

Here are some examples

let old = Array("abc")
let new = Array("bcd")
let changes = diff(old: old, new: new)

// Delete "a" at index 0
// Insert "d" at index 2
let old = Array("abcd")
let new = Array("adbc")
let changes = diff(old: old, new: new)

// Move "d" from index 3 to index 1
let old = [
  User(id: 1, name: "Captain America"),
  User(id: 2, name: "Captain Marvel"),
  User(id: 3, name: "Thor"),
]

let new = [
  User(id: 1, name: "Captain America"),
  User(id: 2, name: "The Binary"),
  User(id: 3, name: "Thor"),
]

let changes = diff(old: old, new: new)

// Replace user "Captain Marvel" with user "The Binary" at index 1

DiffAware protocol

Model must conform to DiffAware protocol for DeepDiff to work. An model needs to be uniquely identified via diffId to tell if there have been any insertions or deletions. In case of same diffId, compareContent is used to check if any properties have changed, this is for replacement changes.

public protocol DiffAware {
  associatedtype DiffId: Hashable

  var diffId: DiffId { get }
  static func compareContent(_ a: Self, _ b: Self) -> Bool
}

Some primitive types like String, Int, Character already conform to DiffAware

Animate UITableView and UICollectionView

Changes to DataSource can be animated by using batch update, as guided in Batch Insertion, Deletion, and Reloading of Rows and Sections

Since Change returned by DeepDiff follows the way batch update works, animating DataSource changes is easy.

For safety, update your data source model inside updateData to ensure synchrony inside performBatchUpdates

let oldItems = items
let newItems = DataSet.generateNewItems()
let changes = diff(old: oldItems, new: newItems)

collectionView.reload(changes: changes, section: 2, updateData: { 
  self.items = newItems
})

Take a look at Demo where changes are made via random number of items, and the items are shuffled.

How does it work

Wagner–Fischer

If you recall from school, there is Levenshtein distance which counts the minimum edit distance to go from one string to another.

Based on that, the first version of DeepDiff implements Wagner–Fischer, which uses dynamic programming to compute the edit steps between 2 strings of characters. DeepDiff generalizes this to make it work for any collection.

Some optimisations made

  • Check empty old or new collection to return early
  • Use diffId to quickly check that 2 items are not equal
  • Follow "We can adapt the algorithm to use less space, O(m) instead of O(mn), since it only requires that the previous row and current row be stored at any one time." to use 2 rows, instead of matrix to reduce memory storage.

The performance greatly depends on the number of items, the changes and the complexity of the equal function.

Wagner–Fischer algorithm has O(mn) complexity, so it should be used for collection with < 100 items.

Heckel

The current version of DeepDiff uses Heckel algorithm as described in A technique for isolating differences between files. It works on 2 observations about line occurrences and counters. The result is a bit lengthy compared to the first version, but it runs in linear time.

Thanks to

More

There are other algorithms that are interesting

Benchmarks

Benchmarking is done on real device iPhone 6, with random items made of UUID strings (36 characters including hyphens), just to make comparisons more difficult.

You can take a look at the code Benchmark. Test is inspired from DiffUtil

Among different frameworks

Here are several popular diffing frameworks to compare

💪 From 2000 items to 2100 items (100 deletions, 200 insertions)

let (old, new) = generate(count: 2000, removeRange: 100..<200, addRange: 1000..<1200)

benchmark(name: "DeepDiff", closure: {
  _ = DeepDiff.diff(old: old, new: new)
})

benchmark(name: "Dwifft", closure: {
  _ = Dwifft.diff(old, new)
})

benchmark(name: "Changeset", closure: {
  _ = Changeset.edits(from: old, to: new)
})

benchmark(name: "Differ", closure: {
  _ = old.diffTraces(to: new)
})

benchmark(name: "ListDiff", closure: {
  _ = ListDiff.List.diffing(oldArray: old, newArray: new)
})

Result

DeepDiff: 0.0450611114501953s
Differ: 0.199673891067505s
Dwifft: 149.603884935379s
Changeset: 77.5895738601685s
ListDiff: 0.105544805526733s

Increasing complexity

Here is how DeepDiff handles large number of items and changes

💪 From 10000 items to 11000 items (1000 deletions, 2000 insertions)

DeepDiff: 0.233131170272827s

💪 From 20000 items to 22000 items (2000 deletions, 4000 insertions)

DeepDiff: 0.453393936157227s

💪 From 50000 items to 55000 items (5000 deletions, 10000 insertions)

DeepDiff: 1.04128122329712s

💪 From 100000 items to 1000000 items

Are you sure?

Installation

CocoaPods

Add the following to your Podfile

pod 'DeepDiff'

Carthage

Add the following to your Cartfile

github "onmyway133/DeepDiff"

Swift Package Manager

Add the following to your Package.swift file

.package(url: "https://github.com/onmyway133/DeepDiff.git", .upToNextMajor(from: "2.3.0"))

DeepDiff can also be installed manually. Just download and drop Sources folders in your project.

Author

Khoa Pham, [email protected]

Contributing

We would love you to contribute to DeepDiff, check the CONTRIBUTING file for more info.

License

DeepDiff is available under the MIT license. See the LICENSE file for more info.

Comments
  • Using HashValue as table key is unreliable

    Using HashValue as table key is unreliable

    I've been testing a variation of your Heckel implementation in a macOS CollectionView project and found an issue. Using hashValue as the key in the table is not reliable. I've made this same mistake myself in early versions of the data structures used in that collection view implementation. My basic solution for now is to replace with [T.Element:TableEntry] and the issue hasn't come up again.

    From the Apple Docs

    A hash value, provided by a type’s hashValue property, is an integer that is the same for any two instances that compare equally. That is, for two instances a and b of the same type, if a == b, then a.hashValue == b.hashValue. The reverse is not true: Two instances with equal hash values are not necessarily equal to each other.

    opened by WCByrne 12
  • Heckel algorithm does not want to do replacements.

    Heckel algorithm does not want to do replacements.

    I'm having trouble getting the default algorithm to do replacements instead of deletes followed by inserts. Even this basic code...

    let old = [
        "New York",
        "Imagine City",
        "London"
    ]
    
    let new = [
        "New York",
        "Oslo",
        "London",
        ]
    
    let changes = diff(old: old, new: new)
    

    ...gives me a delete followed by an insert. Whereas using let changes = diff(old: old, new: new, algorithm: WagnerFischer()) makes everything work correctly.

    Is this an intentional attribute of the Heckel algorithm?

    opened by archagon 10
  • Data source update should be inside performBatchUpdates

    Data source update should be inside performBatchUpdates

    Both UICollectionView+Extensions and UITableView+Extensions need to add closures for updating the data model after performBatchUpdates is called but before updates are applied (Example fix below):

    public func reload<T: Hashable>(
        changes: [Change<T>],
        section: Int = 0,
        updateModel: (() -> Void)? = nil, // <===== This should fix #8 
        completion: ((Bool) -> Void)? = nil) {
        
        let changesWithIndexPath = IndexPathConverter().convert(changes: changes, section: section)
        
        // reloadRows needs to be called outside the batch
        
        performBatchUpdates({
          updateModel?()                  // <===== This should fix #8 
          internalBatchUpdates(changesWithIndexPath: changesWithIndexPath)
        }, completion: { finished in
          completion?(finished)
        })
        
        changesWithIndexPath.replaces.executeIfPresent {
          self.reloadItems(at: $0)
        }
      }
    

    Otherwise, you'll see NSInternalInconsistencyExceptions since the model will already have the same number of items before and after update.

    opened by dsanghan 9
  • Build Library For Distribution (swift compatibility)

    Build Library For Distribution (swift compatibility)

    hi! wondering if you'd be able to set the Build Libraries For Distribution flag to YES in the project's build settings. i use deep diff through Carthage and this would help to ensure support for new swift versions which seem to come with every new xcode release.

    thank you !

    opened by cgmaier 8
  • Need more public API

    Need more public API

    I have try to make extension for ASTableNode & ASCollectionNode (both from ASDK) with DeepDiff. So the ChangeWithIndexPath, IndexPathConverter & Array.executeIfPresent need to be public.

    Could you make it happen?

    opened by nixzhu 8
  • 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of sections.  The number of sections contained in the table view after the update (1) must be equal to the number of sections contained in the table view before the update (0), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted).'

    'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of sections. The number of sections contained in the table view after the update (1) must be equal to the number of sections contained in the table view before the update (0), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted).'

    Hey @onmyway133

    I've faced a crash when using sections in UITableView. I have a regular controller with some array of data

    var data: Array<ParticipantData> = []

    By design I want to use each item of this array as a section and inner array (items) as rows in this section. Here is ParticipantData:

    class ParticipantData {
     let id: Int
     let items: Array<Item>
    
        class func compareContent(_ a: ParticipantData, _ b: ParticipantData) -> Bool {
           // Compare ids and items
            return a.id == b.id && Comparator.compare(a.items, b.items)
        }
    
        public var diffId: Int {
            return self.id
        }
    }
    

    For calculating difference between collections I'm calling following code:

    if (!newData.isEmpty) {
                   // Calculate diff between old and new data
                    let changes = diff(old: self.data, new: newData)
    
                   // deliver results
                  // Neither section: self.data.count nor newData.count works
                    self.tableView.reload(changes: changes, section: self.data.count, replacementAnimation: UITableView.RowAnimation.none, updateData: {
                        self.data = newData
                    })
    } else {
                   // Tried a fix with reloading data when it's empty
                    self.tableView.reloadData()
    }
    

    And code which is responsible for displaying sections and rows:

    extension MainController: UITableViewDataSource, UITableViewDelegate {
        public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
           // Each section has as many rows as it has items 
           return data[section].items.count
        }
    
        public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = UITableViewCell()
            cell.textLabel?.text = "\(indexPath.section): \(indexPath.row)"
            return cell
        }
    
        public func numberOfSections(in tableView: UITableView) -> Int {
            // We have as many sections as items in array
            return data.count
        }
    
        public func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
            return "Title"
        }
    }
    

    As result I have a crash 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of sections. The number of sections contained in the table view after the update (1) must be equal to the number of sections contained in the table view before the update (0), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted).'

    Interesting thing: it's working when there is no numberOfSections method

    opened by sereden 7
  • Example using two String arrays

    Example using two String arrays

    Hey! We're trying to use DeepDiff with UIStackView which does not offer batch updates support as UITableView and UICollectionView. This means that when we get an insert we insert a new arrangedSubview, when we get a delete we delete it, and so on.

    We narrowed down the problem to the following example:

    var old = ["Category3", "Category2", "Category4", "Category5", "Category1"]
    let new = ["Category1", "Category2", "Category3", "Category4", "Category5"]
    
    let algorithm = WagnerFischer<String>()
    let changes = algorithm.diff(old: old, new: new)
    
    changes.forEach { change in
        switch change {
        case .insert(let info):
            old.insert(info.item, at: info.index)
        case .delete(let info):
            old.remove(at: info.index)
        case .move(let info):
            old.remove(at: info.fromIndex)
            old.insert(info.item, at: info.toIndex)
        case .replace(let info):
            old.remove(at: info.index)
            old.insert(info.newItem, at: info.index)
        }
    }
    
    print(old) // ["Category1", "Category2", "Category3", "Category4", "Category1"]
    
    (lldb) po changes
    ▿ replace : Replace<String>
      - oldItem : "Category3"
      - newItem : "Category1"
      - index : 0
    ▿ insert : Insert<String>
      - item : "Category3"
      - index : 2
    ▿ delete : Delete<String>
      - item : "Category1"
      - index : 4
    

    As you can see the output is not correct. We're probably missing something at some point but cannot identify where.

    Let us know. Thanks!

    opened by jakunico 6
  • IndexPath.section is always zero in the results

    IndexPath.section is always zero in the results

    While this issue is related to #1 , what I propose is to make DeepDiff applicable to calculate Diff in any section other than 0. Currently, the section number is hardcoded to be 0.

    What I propose, is to add a section parameter to the API to generate valid IndexPaths, for example:

    let changes = diff(old: oldItems, new: items, section: 4)
    
    opened by richardtop 5
  • lifetime of Heckel?

    lifetime of Heckel?

    hey! I was surprised to see that Heckel didn't have static functions, and instead expects diffs to be carried out on an instance. i noticed that in my usage of the class, i have

        public func update(data: [ListSection], animating: Bool = false, completion: (() -> Void)? = nil) {
            if self.data.isNotEmpty {
                let diff = HeckelDiffingAlgorithm().diff(old: self.data, new: data)
    

    where I create a new instance each time. Is that required? or can I create one instance and reuse it each time my data changes?

    opened by AndrewSB 3
  • How to use diffId when diffing SQL entities?

    How to use diffId when diffing SQL entities?

    Hi, I am trying to use DeepDiff and my models are sql objects that are returned from my backend. To enable the use of deepdiff, I need to be able to return a diffId in the to conform to the protocol DiffAware.

    The problem arises that my sql objects have composite primary keys, meaning that it requires more than just one element to compare each object's uniqueness. It is easy to do checks in the compareContent function, but I am very puzzled to how one would go about declaring the diffId to ensure that these types can return a hash, and that being a string has since all of my attributes are composed of strings.

    An example object is instantiated like Object(place: String, date: String, time: String, reason: String). Where the primary key is composed of the place, date and time values. How would one go about setting the diffId for this object?

    I hope this example makes sense.

    Thank you.

    opened by munibrahman 3
  • DeepDiff too slow when comparing 2 string arrays.

    DeepDiff too slow when comparing 2 string arrays.

    Hi, i am trying to implement a text-base diff viewer, my code looks like the following:

    let originalLines = originalSource.split(separator: "\n", omittingEmptySubsequences: false).map{String($0)}
    
    
    let diffLines = diffSource.split(separator: "\n", omittingEmptySubsequences: false).map{String($0)}
                
                
    let algorithmStart = CACurrentMediaTime()
    let diffchecker = WagnerFischer<String>(reduceMove: false)
    let changes = diffchecker.diff(old: originalLines, new: diffLines)
    DC_LOG_INFO("DiffVC, diff algorithm cost \(CACurrentMediaTime() - algorithmStart)")
    

    my sample files are 2 objc source files with 650+lines, and only 1 line is different , and the log prints out: DiffVC, diff algorithm cost 6.890240641998389

    Any idea of why this is so slow?

    opened by Gocy015 3
  • How to apply the changes to old-array

    How to apply the changes to old-array

    In my situation, I make diffing between two different classes packaged by the same enum. As: enum PackagingEnum: DiffAware { case a(ClassA) case b(ClassB)

    var diffId: Int {
        switch self {
        case a(let classA):
            return classA.hashValue
        case b(let classB)
            return classB.hashValue
        }
    }
    

    } in this case, I want to make diffing [ClassA] to [ClassB] via DeepDiff.

    but when I get the changes by 'diff(old: [Enum-B], new: [Enum-A])'

    I traverse the changes, and invoke 'insert/remove/move' in [Enum-B](the old), I get crashed because of 'Out of Range'

    opened by GloamZz 2
  • Unsupported watchOS version for PackageDescription version

    Unsupported watchOS version for PackageDescription version

    invalidManifestFormat("/var/folders/6q/wgy6jtp12w5gzgm9lzcglpqw0000gn/T/TemporaryFile.rCB6P4.swift:11:19: error: 'v6' is unavailable\n .watchOS(.v6)\n ^~\nPackageDescription.SupportedPlatform:45:27: note: 'v6' was introduced in PackageDescription 5.1\n public static var v6: PackageDescription.SupportedPlatform.WatchOSVersion\n ^", diagnosticFile: Optional(AbsolutePath:"/Users/vagrant/Library/Developer/Xcode/DerivedData/Harper-cxngxloxijxbllfwrovkiaxoqvem/SourcePackages/ManifestLoading/deepdiff.dia")) in https://github.com/onmyway133/DeepDiff

    Introduced in 2.3.2.

    Caused by this change: https://github.com/onmyway133/DeepDiff/commit/86d6b51b47804e380801f4eba45297cb07788b18#diff-f913940c58e8744a2af1c68b909bb6383e49007e6c5a12fb03104a9006ae677eR11

    I'm not getting this on all versions of Xcode. For example, it's working on Xcode 12.5 RC on my machine. But it fails consistently on Bitrise with Xcode 12.4 and 12.5.

    opened by RobinDaugherty 4
  • Should I implement diifId If object is already conforming Hashable?

    Should I implement diifId If object is already conforming Hashable?

    Hello,

    Should I implement diifId If object is already conforming Hashable? I am using RealmSwift and my class conforms Object protocol which also conforms Hashable. My problem is that I have an unique string as primary key and that value is used as hashValue. (I don't know how Realm handles it). Therefore, I believe It generates unique int from that string which is used on hash function. When I add below code, I can't be sure If id is unique every time. Therefore, I don't wanna add it.

        var diffId: Int {
            return identifier.hashValue
        }
    
    opened by emreond 1
  • Suggestion to simplify implementation

    Suggestion to simplify implementation

    We should encourage users to hook into the willSet parameter as the tableview / collection view are finicky and will crash when the underlying data is not atomically set.

    opened by 8secz-johndpope 0
  • Error adding new section to collection view

    Error adding new section to collection view

    Hi, Thanks for the great work done on this. I'm running into an issue adding a new section that did not exist in the first place. Is there any way to handle a diff such that it creates a new section altoegther and then inserts the diff row ?

    Error is below. Invalid update: invalid number of sections. The number of sections contained in the collection view after the update (2) must be equal to the number of sections contained in the collection view before the update (1), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted).

    opened by nikhilcoolstuff 1
Releases(2.3.3)
  • 2.3.1(Nov 12, 2019)

  • 2.3.0(Jun 14, 2019)

  • 2.2.0(May 3, 2019)

  • 2.0.1(Feb 28, 2019)

  • 2.0.0(Feb 26, 2019)

  • 1.4.0(Jan 4, 2019)

  • 1.3.0(Sep 24, 2018)

    • Update project configuration to resolve linking warning in app extension https://github.com/onmyway133/DeepDiff/pull/18, by KhaosT
    • Migrate to swift 4.2 https://github.com/onmyway133/DeepDiff/pull/25, by GrigoryUlanov

    🤘 Closed issues

    • Need more public API https://github.com/onmyway133/DeepDiff/issues/11
    • Use of unresolved identifier 'IndexPathConverter' https://github.com/onmyway133/DeepDiff/issues/17
    • reload changes https://github.com/onmyway133/DeepDiff/issues/7
    • No 'Move' step https://github.com/onmyway133/DeepDiff/issues/15
    • NSFetchedResultsController update UICV https://github.com/onmyway133/DeepDiff/issues/9
    • Heckel algorithm doesn't detect replaces? https://github.com/onmyway133/DeepDiff/issues/16
    • Using HashValue as table key is unreliable https://github.com/onmyway133/DeepDiff/issues/13
    • Can we reduce the speed of shuffle? https://github.com/onmyway133/DeepDiff/issues/22
    • Make completion handler optional similar to other Cocoa API https://github.com/onmyway133/DeepDiff/issues/20
    Source code(tar.gz)
    Source code(zip)
  • 1.2.0(Apr 12, 2018)

    Merged pull requests

    • add iOS 8 support https://github.com/onmyway133/DeepDiff/pull/6, by zlib
    • Texture example is added. https://github.com/onmyway133/DeepDiff/pull/10, by gungorbasa
    • Reload with custom animations https://github.com/onmyway133/DeepDiff/pull/12, by nixzhu

    Closed issues

    • IndexPath.section is always zero in the results https://github.com/onmyway133/DeepDiff/issues/4
    • "append" method ? https://github.com/onmyway133/DeepDiff/issues/3
    • reduceMove changed? https://github.com/onmyway133/DeepDiff/issues/14
    Source code(tar.gz)
    Source code(zip)
  • 1.1.2(Feb 3, 2018)

  • 1.1.1(Jan 22, 2018)

  • 1.1.0(Jan 3, 2018)

  • 1.0.0(Jan 3, 2018)

Owner
Khoa
Check my apps https://onmyway133.com/apps
Khoa
Differific - a fast and convenient diffing framework.

Differific Description Differific is a diffing tool that helps you compare Hashable objects using the Paul Heckel's diffing algorithm. Creating a chan

Christoffer Winterkvist 127 Jun 3, 2022
Fast sorted collections for Swift using in-memory B-trees

Fast Sorted Collections for Swift Using In-Memory B-Trees Overview Reference Documentation Optimizing Collections: The Book What Are B-Trees? Why In-M

null 1.3k Dec 20, 2022
💻 A fast and flexible O(n) difference algorithm framework for Swift collection.

A fast and flexible O(n) difference algorithm framework for Swift collection. The algorithm is optimized based on the Paul Heckel's algorithm. Made wi

Ryo Aoyama 3.3k Jan 4, 2023
Swift-extensions - Swift package extending the Swift programming language.

swift-extensions A package containing extensions for the Swift programming language. Contribution Reporting a bug If you find a bug, please open a bug

Alexandre H. Saad 2 Jun 12, 2022
Commonly used data structures for Swift

Swift Collections is an open-source package of data structure implementations for the Swift programming language.

Apple 2.7k Jan 5, 2023
Examples of commonly used data structures and algorithms in Swift.

Swift Structures This project provides a framework for commonly used data structures and algorithms written in a new iOS development language called S

Wayne Bishop 2.1k Dec 28, 2022
Simple diff library in pure Swift

Diff Simple diffing library in pure Swift. Installing You can use Carthage or Swift Package Manager to install Diff. Usage Start by importing the pack

Sam Soffes 120 Sep 9, 2022
A functional tool-belt for Swift Language similar to Lo-Dash or Underscore.js in Javascript

Dollar Dollar is a Swift library that provides useful functional programming helper methods without extending any built in objects. It is similar to L

Ankur Patel 4.2k Jan 4, 2023
Swift type modelling the success/failure of arbitrary operations.

Result This is a Swift µframework providing Result<Value, Error>. Result<Value, Error> values are either successful (wrapping Value) or failed (wrappi

Antitypical 2.5k Dec 26, 2022
Swift μ-framework for efficient array diffs and datasource adapters.

Buffer Swift μ-framework for efficient array diffs, collection observation and data source implementation. C++11 port here Installation cd {PROJECT_RO

Alex Usbergo 348 Aug 2, 2022
A Graph Data Structure in Pure Swift

SwiftGraph SwiftGraph is a pure Swift (no Cocoa) implementation of a graph data structure, appropriate for use on all platforms Swift supports (iOS, m

David Kopec 700 Dec 16, 2022
A Generic Priority Queue in Pure Swift

SwiftPriorityQueue SwiftPriorityQueue is a pure Swift (no Cocoa) implementation of a generic priority queue data structure, appropriate for use on all

David Kopec 350 Dec 22, 2022
Super lightweight DB written in Swift.

Use of value types is recommended and we define standard values, simple structured data, application state and etc. as struct or enum. Pencil makes us

Naruki Chigira 88 Oct 22, 2022
NSCoding's counterpart for Swift structs.

Dekoter Why You Might Be Interested How Much Familiar It Feels One More Example What We've Learned from It Features Save an Object to UserDefaults Arc

Artem Stepanenko 25 May 15, 2022
Algorithms and data structures in Swift, with explanations!

Welcome to the Swift Algorithm Club! Here you'll find implementations of popular algorithms and data structures in everyone's favorite new language Sw

raywenderlich 27.3k Jan 8, 2023
A Distributed Value Store in Swift.

Impeller is a Distributed Value Store (DVS) written in Swift. It was inspired by successful Distributed Version Control Systems (DVCSes) like Git and

David Coyle 1 Jun 17, 2020
Swift library to generate differences and patches between collections.

Differ Differ generates the differences between Collection instances (this includes Strings!). It uses a fast algorithm (O((N+M)*D)) to do this. Featu

Tony Arnold 628 Dec 29, 2022
A Swift probability and statistics library

Probably Probably is a set of Swift structures for computing the probability and cumulative distributions of different probablistic functions. Right n

Harlan Haskins 270 Dec 2, 2022
The simplest abstraction to synchronize local data with remote source. For iOS, wirtten in swift.

Purpose The simplest abstraction to synchronize local data with remote source. For iOS, written in swift. Overview Many applications uses remote serve

Siarhei Ladzeika 7 Mar 17, 2022