{"id":1041,"url":"https://github.com/tonyarnold/Differ","last_synced_at":"2025-07-30T20:30:59.764Z","repository":{"id":39969503,"uuid":"74619541","full_name":"tonyarnold/Differ","owner":"tonyarnold","description":"Swift library to generate differences and patches between collections.","archived":false,"fork":true,"pushed_at":"2022-03-13T03:48:15.000Z","size":689,"stargazers_count":665,"open_issues_count":23,"forks_count":74,"subscribers_count":12,"default_branch":"main","last_synced_at":"2025-07-06T13:16:35.767Z","etag":null,"topics":["diff","library","swift","swift-package-manager"],"latest_commit_sha":null,"homepage":"https://tonyarnold.github.io/Differ/","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"wokalski/Diff.swift","license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tonyarnold.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-11-23T22:42:15.000Z","updated_at":"2025-07-03T10:04:34.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/tonyarnold/Differ","commit_stats":null,"previous_names":[],"tags_count":28,"template":false,"template_full_name":null,"purl":"pkg:github/tonyarnold/Differ","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonyarnold%2FDiffer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonyarnold%2FDiffer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonyarnold%2FDiffer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonyarnold%2FDiffer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tonyarnold","download_url":"https://codeload.github.com/tonyarnold/Differ/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonyarnold%2FDiffer/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267935168,"owners_count":24168271,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-30T02:00:09.044Z","response_time":70,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["diff","library","swift","swift-package-manager"],"created_at":"2024-01-05T20:15:37.674Z","updated_at":"2025-07-30T20:30:59.430Z","avatar_url":"https://github.com/tonyarnold.png","language":"Swift","readme":"# Differ\n\n[![Continuous Integration](https://github.com/tonyarnold/Differ/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/tonyarnold/Differ/actions/workflows/continuous-integration.yml)\n\nDiffer generates the differences between `Collection` instances (this includes Strings!).\n\nIt uses a [fast algorithm](http://www.xmailserver.org/diff2.pdf) `(O((N+M)*D))` to do this.\n\n## Features\n\n- ⚡️ [It is fast](#performance-notes)\n- Differ supports three types of operations:\n  - Insertions\n  - Deletions\n  - Moves (when using `ExtendedDiff`)\n- Arbitrary sorting of patches (`Patch`)\n- Utilities for updating `UITableView` and `UICollectionView` in UIKit, and `NSTableView` and `NSCollectionView` in AppKit\n- Calculating differences between collections containing collections (use `NestedDiff`)\n\n## Why do I need it?\n\nThere's a lot more to calculating diffs than performing table view animations easily!\n\nWherever you have code that propagates `added`/`removed`/`moved` callbacks from your model to your user interface, you should consider using a library that can calculate differences. Animating small batches of changes is usually going to be faster and provide a more responsive experience than reloading all of your data.\n\nCalculating and acting on differences should also aid you in making a clear separation between data and user interface, and hopefully provide a more declarative approach: your model performs state transition, then your UI code performs appropriate actions based on the calculated differences to that state.\n\n## Diffs, patches and sorting\n\nLet's consider a simple example of using a patch to transform string `\"a\"` into `\"b\"`. The following steps describe the patches required to move between these states:\n\n Change                          | Result\n:--------------------------------|:-------------\nDelete the item at index 0       | `\"\"`\nInsert `b` at index 0            | `\"b\"`\n\nIf we want to perform these operations in different order, simple reordering of the existing patches won't work:\n\n Change                           | Result\n:---------------------------------|:-------\nInsert `b` at index 0             | `\"ba\"`\nDelete the item at index 0        | `\"a\"`\n\n...whoops!\n\nTo get to the correct outcome, we need to shift the order of insertions and deletions so that we get this:\n\n Change                           | Result\n:---------------------------------|:------\nInsert `b` at index 1             | `\"ab\"`\nDelete the item at index 0        | `\"b\"`\n\n### Solution\n\nIn order to mitigate this issue, there are two types of output:\n\n- *Diff*\n  - A sequence of deletions, insertions, and moves (if using `ExtendedDiff`) where deletions point to locations of an item to be deleted in the source and insertions point to the items in the output. Differ produces just one `Diff`.\n- *Patch*\n  - An _ordered sequence_ of steps to be applied to the source collection that will result in the second collection. This is based on a `Diff`, but it can be arbitrarily sorted.\n\n### Practical sorting\n\nIn practice, this means that a diff to transform the string `1234` into `1` could be described as the following set of steps:\n\n    DELETE 1\n    DELETE 2\n    DELETE 3\n\nThe patch to describe the same change would be:\n\n    DELETE 1\n    DELETE 1\n    DELETE 1\n\nHowever, if we decided to sort it so that deletions and higher indices are processed first, we get this patch:\n\n    DELETE 3\n    DELETE 2\n    DELETE 1\n\n## How to use\n\n### Table and Collection Views\n\n\nThe following will automatically animate deletions, insertions, and moves:\n\n```swift\ntableView.animateRowChanges(oldData: old, newData: new)\n\ncollectionView.animateItemChanges(oldData: old, newData: new, updateData: { self.dataSource = new })\n```\n\nIt can work with sections, too!\n```swift\ntableView.animateRowAndSectionChanges(oldData: old, newData: new)\n\ncollectionView.animateItemAndSectionChanges(oldData: old, newData: new, updateData: { self.dataSource = new })\n```\n\nYou can also calculate `diff` separately and use it later:\n```swift\n// Generate the difference first\nlet diff = dataSource.extendedDiff(newDataSource)\n\n// This will apply changes to dataSource.\nlet dataSourceUpdate = { self.dataSource = newDataSource }\n\n// ...\n\ntableView.apply(diff)\n\ncollectionView.apply(diff, updateData: dataSourceUpdate)\n```\n\nPlease see the [included examples](/Examples/) for a working sample.\n\n#### Note about `updateData`\n\nSince version `2.0.0` there is now an `updateData` closure which notifies you when it's an appropriate time to update  `dataSource` of your `UICollectionView`. This addition refers to UICollectionView's [performbatchUpdates](https://developer.apple.com/documentation/uikit/uicollectionview/1618045-performbatchupdates):\n\n\u003e If the collection view's layout is not up to date before you call this method, a reload may occur. To avoid problems, you should update your data model inside the updates block or ensure the layout is updated before you call `performBatchUpdates(_:completion:)`.\n\nThus, it is **recommended** to update your `dataSource` inside `updateData` closure to avoid potential crashes during animations.\n\n### Using Patch and Diff\n\nWhen you want to determine the steps to transform one collection into another (e.g. you want to animate your user interface according to changes in your model), you could do the following:\n\n```swift\nlet from: T\nlet to: T\n\n// patch() only includes insertions and deletions\nlet patch: [Patch\u003cT.Iterator.Element\u003e] = patch(from: from, to: to)\n\n// extendedPatch() includes insertions, deletions and moves\nlet patch: [ExtendedPatch\u003cT.Iterator.Element\u003e] = extendedPatch(from: from, to: to)\n```\n\nWhen you need additional control over ordering, you could use the following:\n\n```swift\nlet insertionsFirst = { element1, element2 -\u003e Bool in\n    switch (element1, element2) {\n    case (.insert(let at1), .insert(let at2)):\n        return at1 \u003c at2\n    case (.insert, .delete):\n        return true\n    case (.delete, .insert):\n        return false\n    case (.delete(let at1), .delete(let at2)):\n        return at1 \u003c at2\n    default: fatalError() // Unreachable\n    }\n}\n\n// Results in a list of patches with insertions preceding deletions\nlet patch = patch(from: from, to: to, sort: insertionsFirst)\n```\n\nAn advanced example: you would like to calculate the difference first, and then generate a patch. In certain cases this can result in a performance improvement.\n\n`D` is the length of a diff:\n\n  - Generating a sorted patch takes `O(D^2)` time.\n  - The default order takes `O(D)` to generate.\n\n```swift\n// Generate the difference first\nlet diff = from.diff(to)\n\n// Now generate the list of patches utilising the diff we've just calculated\nlet patch = diff.patch(from: from, to: to)\n```\n\nIf you'd like to learn more about how this library works, `Graph.playground` is a great place to start.\n\n## Performance notes\n\nDiffer is **fast**. Many of the other Swift diff libraries use a simple `O(n*m)` algorithm, which allocates a 2 dimensional array and then walks through every element. This can use _a lot_ of memory.\n\nIn the following benchmarks, you should see an order of magnitude difference in calculation time between the two algorithms.\n\nEach measurement is the mean time in seconds it takes to calculate a diff, over 10 runs on an iPhone 6.\n\n|         |   Diff    | Dwifft  |\n|---------|:----------|:--------|\n| same    |  0.0213   | 52.3642 |\n| created |  0.0188   | 0.0033  |\n| deleted |  0.0184   | 0.0050  |\n| diff    |  0.1320   | 63.4084 |\n\nYou can run these benchmarks yourself by [checking out the Diff Performance Suite](https://github.com/tonyarnold/DiffPerformanceSuite).\n\nAll of the above being said, the algorithm used by Diff works best for collections with _small_ differences between them. However, even for big differences this library is still likely to be faster than those that use the simple `O(n*m)` algorithm. If you need better performance with large differences between collections, please consider implementing a more suitable approach such as [Hunt \u0026 Szymanski's algorithm](http://par.cse.nsysu.edu.tw/~lcs/Hunt-Szymanski%20Algorithm.php) and/or [Hirschberg's algorithm](https://en.wikipedia.org/wiki/Hirschberg%27s_algorithm).\n\n## Requirements\n\nDiffer requires at least Swift 5.4 or Xcode 12.5 to compile.\n\n## Installation\n\nYou can add Differ to your project using Carthage, CocoaPods, Swift Package Manager, or as an Xcode subproject.\n\n### Carthage\n\n```ruby\ngithub \"tonyarnold/Differ\"\n```\n\n### CocoaPods\n\n```ruby\npod 'Differ'\n```\n\n## Acknowledgements\n\nDiffer is a modified fork of [Wojtek Czekalski's](https://github.com/wokalski) [Diff.swift](https://github.com/wokalski/Diff.swift) - Wojtek deserves all the credit for the original implementation, I am merely its present custodian.\n\nPlease, [file issues with this fork here in this repository](https://github.com/tonyarnold/Diff/issues/new), not in Wojtek's original repository.\n","funding_links":[],"categories":["Data Structures / Algorithms","Swift","swift-package-manager"],"sub_categories":["Getting Started","Other free courses","Linter"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftonyarnold%2FDiffer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftonyarnold%2FDiffer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftonyarnold%2FDiffer/lists"}