{"id":1040,"url":"https://github.com/onmyway133/DeepDiff","last_synced_at":"2025-07-30T20:31:23.925Z","repository":{"id":39006086,"uuid":"109304789","full_name":"onmyway133/DeepDiff","owner":"onmyway133","description":"🦀Amazingly incredible extraordinary lightning fast diffing in Swift","archived":true,"fork":false,"pushed_at":"2021-05-31T03:37:29.000Z","size":7417,"stargazers_count":2051,"open_issues_count":19,"forks_count":145,"subscribers_count":33,"default_branch":"master","last_synced_at":"2024-05-02T05:59:29.322Z","etag":null,"topics":["batch","changes","collection","diff","update"],"latest_commit_sha":null,"homepage":"https://onmyway133.com/apps/","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/onmyway133.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":"onmyway133","patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2017-11-02T18:41:30.000Z","updated_at":"2024-04-16T10:11:23.000Z","dependencies_parsed_at":"2022-07-09T18:30:18.079Z","dependency_job_id":null,"html_url":"https://github.com/onmyway133/DeepDiff","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/onmyway133%2FDeepDiff","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/onmyway133%2FDeepDiff/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/onmyway133%2FDeepDiff/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/onmyway133%2FDeepDiff/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/onmyway133","download_url":"https://codeload.github.com/onmyway133/DeepDiff/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228003665,"owners_count":17854681,"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","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":["batch","changes","collection","diff","update"],"created_at":"2024-01-05T20:15:37.653Z","updated_at":"2024-12-04T19:32:51.747Z","avatar_url":"https://github.com/onmyway133.png","language":"Swift","readme":"# DeepDiff\n\n❤️ Support my apps ❤️ \n\n- [Push Hero - pure Swift native macOS application to test push notifications](https://onmyway133.com/pushhero)\n- [PastePal - Pasteboard, note and shortcut manager](https://onmyway133.com/pastepal)\n- [Quick Check - smart todo manager](https://onmyway133.com/quickcheck)\n- [Alias - App and file shortcut manager](https://onmyway133.com/alias)\n- [My other apps](https://onmyway133.com/apps/)\n\n❤️❤️😇😍🤘❤️❤️\n\n\n[![CI Status](https://img.shields.io/circleci/project/github/onmyway133/DeepDiff.svg)](https://circleci.com/gh/onmyway133/DeepDiff)\n[![Version](https://img.shields.io/cocoapods/v/DeepDiff.svg?style=flat)](http://cocoadocs.org/docsets/DeepDiff)\n[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)\n[![License](https://img.shields.io/cocoapods/l/DeepDiff.svg?style=flat)](http://cocoadocs.org/docsets/DeepDiff)\n[![Platform](https://img.shields.io/cocoapods/p/DeepDiff.svg?style=flat)](http://cocoadocs.org/docsets/DeepDiff)\n![Swift](https://img.shields.io/badge/%20in-swift%204.0-orange.svg)\n\n![](Screenshots/Banner.png)\n\n**DeepDiff** tells the difference between 2 collections and the changes as edit steps. It also supports [Texture](https://github.com/TextureGroup/Texture), see [Texture example](https://github.com/onmyway133/DeepDiff/tree/master/Example/DeepDiffTexture)\n\n- Read more [A better way to update UICollectionView data in Swift with diff framework](https://medium.com/flawless-app-stories/a-better-way-to-update-uicollectionview-data-in-swift-with-diff-framework-924db158db86)\n- Checkout [Micro](https://github.com/onmyway133/Micro) Fast diffing and type safe SwiftUI style data source for UICollectionView\n\n\u003cdiv align = \"center\"\u003e\n\u003cimg src=\"Screenshots/table.gif\" width=\"\" height=\"400\" /\u003e\n\u003cimg src=\"Screenshots/collection.gif\" width=\"\" height=\"400\" /\u003e\n\u003c/div\u003e\n\n## Usage\n\n### Basic\n\nThe result of `diff` is an array of changes, which is `[Change]`. A `Change` can be\n\n- `.insert`: The item was inserted at an index\n- `.delete`: The item was deleted from an index\n- `.replace`: The item at this index was replaced by another item\n- `.move`: The same item has moved from this index to another index\n\nBy 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`.\n\nHere are some examples\n\n```swift\nlet old = Array(\"abc\")\nlet new = Array(\"bcd\")\nlet changes = diff(old: old, new: new)\n\n// Delete \"a\" at index 0\n// Insert \"d\" at index 2\n```\n\n```swift\nlet old = Array(\"abcd\")\nlet new = Array(\"adbc\")\nlet changes = diff(old: old, new: new)\n\n// Move \"d\" from index 3 to index 1\n```\n\n```swift\nlet old = [\n  User(id: 1, name: \"Captain America\"),\n  User(id: 2, name: \"Captain Marvel\"),\n  User(id: 3, name: \"Thor\"),\n]\n\nlet new = [\n  User(id: 1, name: \"Captain America\"),\n  User(id: 2, name: \"The Binary\"),\n  User(id: 3, name: \"Thor\"),\n]\n\nlet changes = diff(old: old, new: new)\n\n// Replace user \"Captain Marvel\" with user \"The Binary\" at index 1\n```\n\n### DiffAware protocol\n\nModel 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. \n\n```swift\npublic protocol DiffAware {\n  associatedtype DiffId: Hashable\n\n  var diffId: DiffId { get }\n  static func compareContent(_ a: Self, _ b: Self) -\u003e Bool\n}\n```\n\nSome primitive types like `String`, `Int`, `Character` already conform to `DiffAware`\n\n### Animate UITableView and UICollectionView\n\nChanges to `DataSource` can be animated by using batch update, as guided in [Batch Insertion, Deletion, and Reloading of Rows and Sections](https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/TableView_iPhone/ManageInsertDeleteRow/ManageInsertDeleteRow.html#//apple_ref/doc/uid/TP40007451-CH10-SW9)\n\nSince `Change` returned by `DeepDiff` follows the way batch update works, animating `DataSource` changes is easy.\n\nFor safety, update your data source model inside `updateData` to ensure synchrony inside `performBatchUpdates`\n\n```swift\nlet oldItems = items\nlet newItems = DataSet.generateNewItems()\nlet changes = diff(old: oldItems, new: newItems)\n\ncollectionView.reload(changes: changes, section: 2, updateData: { \n  self.items = newItems\n})\n```\n\nTake a look at [Demo](https://github.com/onmyway133/DeepDiff/tree/master/Example/DeepDiffDemo) where changes are made via random number of items, and the items are shuffled.\n\n## How does it work\n\n### [Wagner–Fischer](https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm)\n\nIf you recall from school, there is [Levenshtein distance](https://en.wikipedia.org/wiki/Levenshtein_distance) which counts the minimum edit distance to go from one string to another.\n\nBased on that, the first version of `DeepDiff` implements Wagner–Fischer, which uses [dynamic programming](https://en.wikipedia.org/wiki/Dynamic_programming) to compute the edit steps between 2 strings of characters. `DeepDiff` generalizes this to make it work for any collection.\n\nSome optimisations made\n\n- Check empty old or new collection to return early\n- Use `diffId` to quickly check that 2 items are not equal\n- 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.\n\nThe performance greatly depends on the number of items, the changes and the complexity of the `equal` function.\n\n`Wagner–Fischer algorithm` has O(mn) complexity, so it should be used for collection with \u003c 100 items.\n\n### Heckel\n\nThe current version of `DeepDiff` uses Heckel algorithm as described in [A technique for isolating differences between files](https://dl.acm.org/citation.cfm?id=359467). 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.\n\nThanks to\n\n- [Isolating Differences Between Files](https://gist.github.com/ndarville/3166060) for explaining step by step\n- [HeckelDiff](https://github.com/mcudich/HeckelDiff) for a clever move reducer based on tracking `deleteOffset`\n\n### More\n\nThere are other algorithms that are interesting\n\n- [An O(ND) Difference Algorithm and Its Variations](http://www.xmailserver.org/diff2.pdf)\n- [An O(NP) Sequence Comparison Algorithm](https://publications.mpi-cbg.de/Wu_1990_6334.pdf)\n\n## Benchmarks\n\nBenchmarking is done on real device iPhone 6, with random items made of UUID strings (36 characters including hyphens), just to make comparisons more difficult.\n\nYou can take a look at the code [Benchmark](https://github.com/onmyway133/DeepDiff/tree/master/Example/Benchmark). Test is inspired from [DiffUtil](https://developer.android.com/reference/android/support/v7/util/DiffUtil.html)\n\n### Among different frameworks\n\nHere are several popular diffing frameworks to compare\n\n- [Differ](https://github.com/tonyarnold/Differ) 1.0.3, originally [Diff.swift](https://github.com/wokalski/Diff.swift)\n- [Changeset](https://github.com/osteslag/Changeset) 3.0\n- [Dwifft](https://github.com/jflinter/Dwifft) 0.8\n- [ListDiff](https://github.com/lxcid/ListDiff) 0.1.0, port from [IGListKit](https://github.com/Instagram/IGListKit/)\n\n💪 From 2000 items to 2100 items (100 deletions, 200 insertions)\n\n```swift\nlet (old, new) = generate(count: 2000, removeRange: 100..\u003c200, addRange: 1000..\u003c1200)\n\nbenchmark(name: \"DeepDiff\", closure: {\n  _ = DeepDiff.diff(old: old, new: new)\n})\n\nbenchmark(name: \"Dwifft\", closure: {\n  _ = Dwifft.diff(old, new)\n})\n\nbenchmark(name: \"Changeset\", closure: {\n  _ = Changeset.edits(from: old, to: new)\n})\n\nbenchmark(name: \"Differ\", closure: {\n  _ = old.diffTraces(to: new)\n})\n\nbenchmark(name: \"ListDiff\", closure: {\n  _ = ListDiff.List.diffing(oldArray: old, newArray: new)\n})\n```\n\n**Result**\n\n```\nDeepDiff: 0.0450611114501953s\nDiffer: 0.199673891067505s\nDwifft: 149.603884935379s\nChangeset: 77.5895738601685s\nListDiff: 0.105544805526733s\n```\n\n![](Screenshots/benchmark3d.png)\n\n### Increasing complexity\n\nHere is how `DeepDiff` handles large number of items and changes\n\n💪 From 10000 items to 11000 items (1000 deletions, 2000 insertions)\n\n```\nDeepDiff: 0.233131170272827s\n```\n\n💪 From 20000 items to 22000 items (2000 deletions, 4000 insertions)\n\n```\nDeepDiff: 0.453393936157227s\n```\n\n💪 From 50000 items to 55000 items (5000 deletions, 10000 insertions)\n\n```\nDeepDiff: 1.04128122329712s\n```\n\n💪 From 100000 items to 1000000 items\n\n```\nAre you sure?\n```\n\n## Installation\n\n### CocoaPods\n\nAdd the following to your Podfile\n\n```ruby\npod 'DeepDiff'\n```\n\n### Carthage\n\nAdd the following to your Cartfile\n\n```ruby\ngithub \"onmyway133/DeepDiff\"\n```\n\n### Swift Package Manager\nAdd the following to your Package.swift file\n\n```swift\n.package(url: \"https://github.com/onmyway133/DeepDiff.git\", .upToNextMajor(from: \"2.3.0\"))\n```\n\n**DeepDiff** can also be installed manually. Just download and drop `Sources` folders in your project.\n\n## Author\n\nKhoa Pham, onmyway133@gmail.com\n\n## Contributing\n\nWe would love you to contribute to **DeepDiff**, check the [CONTRIBUTING](https://github.com/onmyway133/DeepDiff/blob/master/CONTRIBUTING.md) file for more info.\n\n## License\n\n**DeepDiff** is available under the MIT license. See the [LICENSE](https://github.com/onmyway133/DeepDiff/blob/master/LICENSE.md) file for more info.\n","funding_links":["https://github.com/sponsors/onmyway133"],"categories":["Data Structures / Algorithms","Libs","Utility [🔝](#readme)","iOS","update","Swift"],"sub_categories":["Getting Started","Utility","Other free courses","Linter","DataSources"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fonmyway133%2FDeepDiff","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fonmyway133%2FDeepDiff","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fonmyway133%2FDeepDiff/lists"}