Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/ra1028/DifferenceKit
💻 A fast and flexible O(n) difference algorithm framework for Swift collection.
https://github.com/ra1028/DifferenceKit
algorithm changeset collectionview diff difference diffing paul-heckel-algorithm tableview
Last synced: 3 days ago
JSON representation
💻 A fast and flexible O(n) difference algorithm framework for Swift collection.
- Host: GitHub
- URL: https://github.com/ra1028/DifferenceKit
- Owner: ra1028
- License: apache-2.0
- Created: 2018-08-03T12:29:06.000Z (over 6 years ago)
- Default Branch: master
- Last Pushed: 2024-02-11T15:52:17.000Z (10 months ago)
- Last Synced: 2024-10-29T15:34:32.200Z (about 1 month ago)
- Topics: algorithm, changeset, collectionview, diff, difference, diffing, paul-heckel-algorithm, tableview
- Language: Swift
- Homepage: https://ra1028.github.io/DifferenceKit
- Size: 8.76 MB
- Stars: 3,563
- Watchers: 41
- Forks: 241
- Open Issues: 31
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- Funding: .github/FUNDING.yml
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
- awesome-ios - DifferenceKit - A fast and flexible O(n) difference algorithm framework for Swift collection. (Data Structures / Algorithms / Getting Started)
- awesome-swift - DifferenceKit - 💻 A fast and flexible O(n) difference algorithm framework. (Libs / Utility)
- awesome-swift - DifferenceKit - 💻 A fast and flexible O(n) difference algorithm framework. (Libs / Utility)
- awesome-ios-star - DifferenceKit - A fast and flexible O(n) difference algorithm framework for Swift collection. (Data Structures / Algorithms / Getting Started)
- fucking-awesome-swift - DifferenceKit - 💻 A fast and flexible O(n) difference algorithm framework. (Libs / Utility)
- awesome - DifferenceKit - 💻 A fast and flexible O(n) difference algorithm framework for Swift collection. (tableview)
- fucking-awesome-ios - DifferenceKit - A fast and flexible O(n) difference algorithm framework for Swift collection. (Data Structures / Algorithms / Getting Started)
- fucking-awesome-ios - DifferenceKit - A fast and flexible O(n) difference algorithm framework for Swift collection. (Data Structures / Algorithms / Getting Started)
- awesome-swift - DifferenceKit - A fast and flexible O(n) difference algorithm framework for Swift collection. ` 📝 2 months ago ` (Utility [🔝](#readme))
- awesome - DifferenceKit - 💻 A fast and flexible O(n) difference algorithm framework for Swift collection. (diff)
- awesome - DifferenceKit - 💻 A fast and flexible O(n) difference algorithm framework for Swift collection. (iOS / DataSources)
- awesome - DifferenceKit - 💻 A fast and flexible O(n) difference algorithm framework for Swift collection. (iOS / DataSources)
README
A fast and flexible O(n) difference algorithm framework for Swift collection.
The algorithm is optimized based on the Paul Heckel's algorithm.
Made with ❤️ by Ryo Aoyama and Contributors
---
## Features
💡 Fastest **O(n)** diffing algorithm optimized for Swift collection
💡 Calculate diffs for batch updates of list UI in `UIKit`, `AppKit` and [Texture](https://github.com/TextureGroup/Texture)
💡 Supports both linear and sectioned collection even if contains duplicates
💡 Supports **all kind of diffs** for animated UI batch updates
---
## Algorithm
This is a diffing algorithm developed for [Carbon](https://github.com/ra1028/Carbon), works stand alone.
The algorithm optimized based on the Paul Heckel's algorithm.
See also his paper ["A technique for isolating differences between files"](https://dl.acm.org/citation.cfm?id=359467) released in 1978.
It allows all kind of diffs to be calculated in linear time **O(n)**.
[RxDataSources](https://github.com/RxSwiftCommunity/RxDataSources) and [IGListKit](https://github.com/Instagram/IGListKit) are also implemented based on his algorithm.However, in `performBatchUpdates` of `UITableView`, `UICollectionView`, etc, there are combinations of diffs that cause crash when applied simultaneously.
To solve this problem, `DifferenceKit` takes an approach of split the set of diffs at the minimal stages that can be perform batch updates with no crashes.Implementation is [here](https://github.com/ra1028/DifferenceKit/blob/master/Sources/Algorithm.swift).
---
## Getting Started
- [API Documentation](https://ra1028.github.io/DifferenceKit)
- [Example Apps](https://github.com/ra1028/DifferenceKit/blob/master/Examples)
- [Benchmark](https://github.com/ra1028/DifferenceKit/blob/master/Benchmark)
- [Playground](https://github.com/ra1028/DifferenceKit/blob/master/DifferenceKit.playground/Contents.swift)## Basic Usage
The type of the element that to take diffs must be conform to the `Differentiable` protocol.
The `differenceIdentifier`'s type is generic associated type:
```swift
struct User: Differentiable {
let id: Int
let name: Stringvar differenceIdentifier: Int {
return id
}func isContentEqual(to source: User) -> Bool {
return name == source.name
}
}
```In the case of definition above, `id` uniquely identifies the element and get to know the user updated by comparing equality of `name` of the elements in source and target.
There are default implementations of `Differentiable` for the types that conforming to `Equatable` or `Hashable`:
```swift
// If `Self` conforming to `Hashable`.
var differenceIdentifier: Self {
return self
}// If `Self` conforming to `Equatable`.
func isContentEqual(to source: Self) -> Bool {
return self == source
}
```
Therefore, you can simply:
```swift
extension String: Differentiable {}
```Calculate the diffs by creating `StagedChangeset` from two collections of elements conforming to `Differentiable`:
```swift
let source = [
User(id: 0, name: "Vincent"),
User(id: 1, name: "Jules")
]
let target = [
User(id: 1, name: "Jules"),
User(id: 0, name: "Vincent"),
User(id: 2, name: "Butch")
]let changeset = StagedChangeset(source: source, target: target)
```If you want to include multiple types conforming to `Differentiable` in the collection, use `AnyDifferentiable`:
```swift
let source = [
AnyDifferentiable("A"),
AnyDifferentiable(User(id: 0, name: "Vincent"))
]
```In the case of sectioned collection, the section itself must have a unique identifier and be able to compare whether there is an update.
So each section must conforming to `DifferentiableSection` protocol, but in most cases you can use `ArraySection` that general type conforming to it.
`ArraySection` requires a model conforming to `Differentiable` for diffing from other sections:
```swift
enum Model: Differentiable {
case a, b, c
}let source: [ArraySection] = [
ArraySection(model: .a, elements: ["A", "B"]),
ArraySection(model: .b, elements: ["C"])
]
let target: [ArraySection] = [
ArraySection(model: .c, elements: ["D", "E"]),
ArraySection(model: .a, elements: ["A"]),
ArraySection(model: .b, elements: ["B", "C"])
]let changeset = StagedChangeset(source: source, target: target)
```You can perform diffing batch updates of `UITableView` and `UICollectionView` using the created `StagedChangeset`.
⚠️ **Don't forget** to **synchronously** update the data referenced by the data-source, with the data passed in the `setData` closure. The diffs are applied in stages, and failing to do so is bound to create a crash:
```swift
tableView.reload(using: changeset, with: .fade) { data in
dataSource.data = data
}
```Batch updates using too large amount of diffs may adversely affect to performance.
Returning `true` with `interrupt` closure then falls back to `reloadData`:
```swift
collectionView.reload(using: changeset, interrupt: { $0.changeCount > 100 }) { data in
dataSource.data = data
}
```
[See More Usage]---
## Comparison with Other Frameworks
Made a fair comparison as much as possible in performance and features with other **popular** and **awesome** frameworks.
This does **NOT** determine superiority or inferiority of the frameworks.
I know that each framework has different benefits.
The frameworks and its version that compared is below.- [DifferenceKit](https://github.com/ra1028/DifferenceKit) - master
- [RxDataSources](https://github.com/RxSwiftCommunity/RxDataSources) ([Differentiator](https://github.com/RxSwiftCommunity/RxDataSources/tree/master/Sources/Differentiator)) - 4.0.1
- [FlexibleDiff](https://github.com/RACCommunity/FlexibleDiff) - 0.0.8
- [IGListKit](https://github.com/Instagram/IGListKit) - 3.4.0
- [DeepDiff](https://github.com/onmyway133/DeepDiff) - 2.2.0
- [Differ](https://github.com/tonyarnold/Differ) ([Diff.swift](https://github.com/wokalski/Diff.swift)) - 1.4.3
- [Dwifft](https://github.com/jflinter/Dwifft) - 0.9
- [Swift.CollectionDifference](https://developer.apple.com/documentation/swift/collectiondifference) - Swift 5.1### Performance Comparison
Benchmark project is [here](https://github.com/ra1028/DifferenceKit/blob/master/Benchmark).
Performance was mesured by code compiled using `Xcode11.1` and `Swift 5.1` with `-O` optimization and run on `iPhone11 Pro simulator`.
Use `Foundation.UUID` as an element of collections.#### - From 5,000 elements to 1,000 deleted, 1,000 inserted and 200 shuffled
| |Time(sec) |
|:-------------------------|-------------------------:|
|DifferenceKit |`0.0019` |
|RxDataSources |`0.0074` |
|IGListKit |`0.0346` |
|FlexibleDiff |`0.0161` |
|DeepDiff |`0.0373` |
|Differ |`1.0581` |
|Dwifft |`0.4732` |
|Swift.CollectionDifference|`0.0620` |#### - From 100,000 elements to 10,000 deleted, 10,000 inserted and 2,000 shuffled
| |Time(sec) |
|:-------------------------|-------------------------:|
|DifferenceKit |`0.0348` |
|RxDataSources |`0.1024` |
|IGListKit |`0.7002` |
|FlexibleDiff |`0.2189` |
|DeepDiff |`0.5537` |
|Differ |`153.8007` |
|Dwifft |`187.1341` |
|Swift.CollectionDifference|`5.0281` |### Features Comparison
#### - Algorithm
| |Base algorithm|Order|
|:-------------------------|-------------:|----:|
|DifferenceKit |Heckel |O(N) |
|RxDataSources |Heckel |O(N) |
|FlexibleDiff |Heckel |O(N) |
|IGListKit |Heckel |O(N) |
|DeepDiff |Heckel |O(N) |
|Differ |Myers |O(ND)|
|Dwifft |Myers |O(ND)|
|Swift.CollectionDifference|Myers |O(ND)|\* [**Heckel algorithm**](https://dl.acm.org/citation.cfm?id=359467)
\* [**Myers algorithm**](http://www.xmailserver.org/diff2.pdf)#### - Supported Collection
| |Linear|Sectioned|Duplicate element/section|
|:-------------------------|:----:|:-------:|:-----------------------:|
|DifferenceKit |✅ |✅ |✅ |
|RxDataSources |❌ |✅ |❌ |
|FlexibleDiff |✅ |✅ |✅ |
|IGListKit |✅ |❌ |✅ |
|DeepDiff |✅ |❌ |✅ |
|Differ |✅ |✅ |✅ |
|Dwifft |✅ |✅ |✅ |
|Swift.CollectionDifference|✅ |❌ |✅ |\* **Linear** means 1-dimensional collection
\* **Sectioned** means 2-dimensional collection#### - Supported Element Diff
| |Delete|Insert|Move|Reload|Move across sections|
|:-------------------------|:----:|:----:|:--:|:----:|:------------------:|
|DifferenceKit |✅ |✅ |✅ |✅ |✅ |
|RxDataSources |✅ |✅ |✅ |✅ |✅ |
|FlexibleDiff |✅ |✅ |✅ |✅ |❌ |
|IGListKit |✅ |✅ |✅ |✅ |❌ |
|DeepDiff |✅ |✅ |✅ |✅ |❌ |
|Differ |✅ |✅ |✅ |❌ |❌ |
|Dwifft |✅ |✅ |❌ |❌ |❌ |
|Swift.CollectionDifference|✅ |✅ |✅ |❌ |❌ |#### - Supported Section Diff
| |Delete|Insert|Move|Reload|
|:-------------------------|:----:|:----:|:--:|:----:|
|DifferenceKit |✅ |✅ |✅ |✅ |
|RxDataSources |✅ |✅ |✅ |❌ |
|FlexibleDiff |✅ |✅ |✅ |✅ |
|IGListKit |❌ |❌ |❌ |❌ |
|DeepDiff |❌ |❌ |❌ |❌ |
|Differ |✅ |✅ |✅ |❌ |
|Dwifft |✅ |✅ |❌ |❌ |
|Swift.CollectionDifference|❌ |❌ |❌ |❌ |---
## Requirements
- Swift 4.2+
- iOS 9.0+
- tvOS 9.0+
- OS X 10.9+
- watchOS 2.0+ (only algorithm)---
## Installation
### [CocoaPods](https://cocoapods.org/)
To use only algorithm without extensions for UI, add the following to your `Podfile`:
```ruby
pod 'DifferenceKit/Core'
```#### iOS / tvOS
To use DifferenceKit with UIKit extension, add the following to your `Podfile`:
```ruby
pod 'DifferenceKit'
```
or
```ruby
pod 'DifferenceKit/UIKitExtension'
```#### macOS
To use DifferenceKit with AppKit extension, add the following to your `Podfile`:
```ruby
pod 'DifferenceKit/AppKitExtension'
```#### watchOS
There is no UI extension for watchOS.
To use only algorithm without extensions for UI, add the following to your `Podfile`:
```ruby
pod 'DifferenceKit/Core'
```### [Carthage](https://github.com/Carthage/Carthage)
Add the following to your `Cartfile`:
```ruby
github "ra1028/DifferenceKit"
```### [Swift Package Manager for Apple platforms](https://developer.apple.com/documentation/xcode/adding_package_dependencies_to_your_app)
Select Xcode menu `File > Swift Packages > Add Package Dependency` and enter repository URL with GUI.
```
Repository: https://github.com/ra1028/DifferenceKit
```### [Swift Package Manager](https://swift.org/package-manager/)
Add the following to the dependencies of your `Package.swift`:
```swift
.package(url: "https://github.com/ra1028/DifferenceKit.git", from: "version")
```---
## Contribution
Pull requests, bug reports and feature requests are welcome 🚀
Please see the [CONTRIBUTING](https://github.com/ra1028/DifferenceKit/blob/master/CONTRIBUTING.md) file for learn how to contribute to DifferenceKit.
---## Credit
#### Bibliography
DifferenceKit was developed with reference to the following excellent materials and framework.- [A technique for isolating differences between files](https://dl.acm.org/citation.cfm?id=359467) (by [Paul Heckel](https://dl.acm.org/author_page.cfm?id=81100051772))
- [DifferenceAlgorithmComparison](https://github.com/horita-yuya/DifferenceAlgorithmComparison) (by [@horita-yuya](https://github.com/horita-yuya))#### OSS using DifferenceKit
The list of the awesome OSS which uses this library. They also help to understanding how to use DifferenceKit.- [Carbon](https://github.com/ra1028/Carbon) (by [@ra1028](https://github.com/ra1028))
- [DiffableDataSources](https://github.com/ra1028/DiffableDataSources) (by [@ra1028](https://github.com/ra1028))
- [Rocket.Chat.iOS](https://github.com/RocketChat/Rocket.Chat.iOS) (by [RocketChat](https://github.com/RocketChat))
- [wire-ios](https://github.com/wireapp/wire-ios) (by [Wire Swiss GmbH](https://github.com/wireapp))
- [ReactiveLists](https://github.com/plangrid/ReactiveLists) (by [PlanGrid](https://github.com/plangrid))
- [ReduxMovieDB](https://github.com/cardoso/ReduxMovieDB) (by [@cardoso](https://github.com/cardoso))
- [TetrisDiffingCompetition](https://github.com/skagedal/TetrisDiffingCompetition) (by [@skagedal](https://github.com/skagedal))#### Other diffing libraries
I respect and ️❤️ all libraries involved in diffing.- [RxDataSources](https://github.com/RxSwiftCommunity/RxDataSources) (by [@kzaher](https://github.com/kzaher), [RxSwift Community](https://github.com/RxSwiftCommunity))
- [IGListKit](https://github.com/Instagram/IGListKit) (by [Instagram](https://github.com/Instagram))
- [FlexibleDiff](https://github.com/RACCommunity/FlexibleDiff) (by [@andersio](https://github.com/andersio), [RACCommunity](https://github.com/RACCommunity))
- [DeepDiff](https://github.com/onmyway133/DeepDiff) (by [@onmyway133](https://github.com/onmyway133))
- [Differ](https://github.com/tonyarnold/Differ) (by [@tonyarnold](https://github.com/tonyarnold))
- [Dwifft](https://github.com/jflinter/Dwifft) (by [@jflinter](https://github.com/jflinter))
- [Changeset](https://github.com/osteslag/Changeset) (by [@osteslag](https://github.com/osteslag))---
## License
DifferenceKit is released under the [Apache 2.0 License](https://github.com/ra1028/DifferenceKit/blob/master/LICENSE).