{"id":1020,"url":"https://github.com/osteslag/Changeset","last_synced_at":"2025-07-30T20:30:43.644Z","repository":{"id":56905797,"uuid":"48755327","full_name":"osteslag/Changeset","owner":"osteslag","description":"Minimal edits from one collection to another","archived":false,"fork":false,"pushed_at":"2020-07-28T10:16:44.000Z","size":550,"stargazers_count":802,"open_issues_count":3,"forks_count":42,"subscribers_count":16,"default_branch":"master","last_synced_at":"2024-11-29T05:47:42.349Z","etag":null,"topics":["change","changes","changeset","delta","diff","ios","macos","minimal-edits","uicollectionview","uitableview"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/osteslag.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-12-29T16:02:50.000Z","updated_at":"2024-08-10T00:29:57.000Z","dependencies_parsed_at":"2022-08-20T19:20:21.483Z","dependency_job_id":null,"html_url":"https://github.com/osteslag/Changeset","commit_stats":null,"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osteslag%2FChangeset","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osteslag%2FChangeset/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osteslag%2FChangeset/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osteslag%2FChangeset/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/osteslag","download_url":"https://codeload.github.com/osteslag/Changeset/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228179105,"owners_count":17881134,"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":["change","changes","changeset","delta","diff","ios","macos","minimal-edits","uicollectionview","uitableview"],"created_at":"2024-01-05T20:15:37.191Z","updated_at":"2024-12-04T19:32:34.640Z","avatar_url":"https://github.com/osteslag.png","language":"Swift","readme":"# Changeset\n\n\u003e _[Changeset – pretty awesome little project](https://twitter.com/joeldev/status/685253183992500225)_  \n\u003e — [Joel Levin](https://github.com/joeldev)\n\nThis is an attempt at implementing the solution outlined in [Dave DeLong](https://github.com/davedelong)’s article, [Edit distance and edit steps](http://davedelong.tumblr.com/post/134367865668/edit-distance-and-edit-steps).\n\nA `Changeset` describes the minimal edits required to go from one `Collection` of `Equatable` elements to another.\n\nIt has been written primarily to be used in conjunction with `UITableView` and `UICollectionView` data sources by detecting additions, deletions, substitutions, and moves between the two sets of data. But it can also be used to compute more general changes between two data sets.\n\n## Usage\n\nThe following code computes the minimal edits of the canonical example, going from the `String` collections “kitten” to “sitting”:\n\n```swift\nlet changeset = Changeset(source: \"kitten\", target: \"sitting\")\n\nprint(changeset)\n// 'kitten' -\u003e 'sitting':\n//     replace with s at offset 0\n//     replace with i at offset 4\n//     insert g at offset 6\n```\n\nThe following assertion would then succeed:\n\n```swift\nlet edits = [\n    Changeset\u003cString\u003e.Edit(operation: .substitution, value: \"s\", destination: 0),\n    Changeset\u003cString\u003e.Edit(operation: .substitution, value: \"i\", destination: 4),\n    Changeset\u003cString\u003e.Edit(operation: .insertion, value: \"g\", destination: 6),\n]\nassert(changeset.edits == edits)\n```\n\nIf you don’t want the overhead of `Changeset` itself, which also stores the source and target collections, you can call `edits` directly (here with [example data](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/TableView_iPhone/ManageInsertDeleteRow/ManageInsertDeleteRow.html#//apple_ref/doc/uid/TP40007451-CH10-SW16) from Apple’s [Table View Programming Guide for iOS](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/TableView_iPhone/AboutTableViewsiPhone/AboutTableViewsiPhone.html)):\n\n```swift\nlet source = [\"Arizona\", \"California\", \"Delaware\", \"New Jersey\", \"Washington\"]\nlet target = [\"Alaska\", \"Arizona\", \"California\", \"Georgia\", \"New Jersey\", \"Virginia\"]\nlet edits = Changeset.edits(from: source, to: target)\n\nprint(edits)\n// [insert Alaska at offset 0, replace with Georgia at offset 2, replace with Virginia at offset 4]\n```\n\nNote that Changeset uses offsets, not indices, to refer to elements in the collections. This is mainly because Swift collections aren’t guaranteed to use zero-based integer indices. See discussion in [issue #37](https://github.com/osteslag/Changeset/issues/37) for more details.\n\n## UIKit Integration\n\nThe offset values can be used directly in the animation blocks of `beginUpdates`/`endUpdates` on `UITableView` and `performBatchUpdates` on `UICollectionView` in that `Changeset` follows the principles explained under [_Batch Insertion, Deletion, and Reloading of Rows and Sections_](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/TableView_iPhone/ManageInsertDeleteRow/ManageInsertDeleteRow.html#//apple_ref/doc/uid/TP40007451-CH10-SW9) in Apple’s guide.\n\nIn short; first all deletions and substitutions are made, relative to the source collection, then, relative to the resulting collection, insertions. A move is just a deletion followed by an insertion.\n\nIn the iOS framework, two convenience extensions (one on `UITableView` and one on `UICollectionView`) have been included to make animated table/collection view updates a breeze. Just call `update`, like this:\n\n```swift\ntableView.update(with: changeset.edits)\n```\n\n## Custom Comparator\n\nBy default a `Changeset` uses `==` to compare elements, but you can write your own comparator, illustrated below, where the occurence of an “a” always triggers a change:\n\n```swift\nlet alwaysChangeA: (Character, Character) -\u003e Bool = {\n    if $0 == \"a\" || $1 == \"a\" {\n        return false\n    } else {\n        return $0 == $1\n    }\n}\nlet changeset = Changeset(source: \"ab\", target: \"ab\", comparator: alwaysChangeA)\n```\n\nAs a result, the changeset will consist of a substitution of the “a” (to another “a”):\n\n```swift\nlet expectedEdits: [Changeset\u003cString\u003e.Edit] = [Changeset.Edit(operation: .substitution, value: \"a\", destination: 0)]\nassert(changeset.edits == expectedEdits)\n```\n\nOne possible use of this is when a cell in a `UITableView` or `UICollectionView` shouldn’t animate when they change.\n\n## Test App\n\nThe Xcode project also contains a target to illustrate the usage in an app:\n\n![Test App](Test%20App/Screen.gif \"Test App\")\n\nThis uses the extensions mentioned above to animate transitions based on the edits of a `Changeset`.\n\n## License\n\nThis project is available under [The MIT License](http://opensource.org/licenses/MIT).  \nCopyright © 2015-18, [Joachim Bondo](mailto:joachim@bondo.net). See [LICENSE](LICENSE.md) file.\n","funding_links":[],"categories":["Data Structures / Algorithms"],"sub_categories":["Getting Started","Other free courses","Linter"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fosteslag%2FChangeset","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fosteslag%2FChangeset","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fosteslag%2FChangeset/lists"}