DeepDiff
DeepDiff比较两个集合之间的差异,并以编辑步骤的形式呈现更改。它也支持Texture,请参阅Texture示例
使用
基本
diff
的结果是一个包含更改的数组,它是[Change]
。一个Change
可以是以下之一:
.insert
:项目被插入到索引位置.delete
:项目从索引位置被删除.replace
:此索引位置的项目被另一个项目替换.move
:同一个项目从当前位置移动到另一个位置
默认情况下,没有.move
。但由于.move
只是.delete
后跟相同项目的.insert
,可以通过将reduceMove
设为true
来减少它。
以下是一些例子
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协议
模型必须符合 DiffAware
协议,DeepDiff 才能工作。一个模型需要通过 diffId
唯一标识来检查是否有插入或删除操作。如果 diffId
相同,则使用 compareContent
来检查是否有属性更改,这是用于替换更改的。
public protocol DiffAware {
associatedtype DiffId: Hashable
var diffId: DiffId { get }
static func compareContent(_ a: Self, _ b: Self) -> Bool
}
一些基本类型,如 String
、Int
、Character
,已经符合 DiffAware
动画 UITableView 和 UICollectionView
可以通批处理更新来动画化对 DataSource
的更改,如批处理插入、删除和重新加载行和分区所述。
由于 DeepDiff
返回的 Change
遵循批处理更新的方式,因此动画化 DataSource
的更改很简单。
为了安全起见,请在 updateData
中更新数据源模型,以确保在 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
})
请参阅演示,其中通过随机数量的项目进行更改,并且项目被打乱。
如何工作
Wagner–Fischer
如果你记得学校里的东西,有 Levenshtein 距离,它计算从一个字符串转换到另一个字符串的最小编辑距离。
基于这个想法,DeepDiff
的第一个版本实现了 Wagner–Fischer,它使用 动态规划来计算两个字符字符串之间的编辑步骤。《DeepDiff》将此泛化以使其适用于任何集合。
进行的一些优化
- 检查空旧或新集合以提前返回
- 使用
diffId
快速检查两个项目是否不相等 - 遵循“我们可以将算法调整以使用更少的空间,O(m) 而不是 O(mn),因为它只需要在任何时候存储前一行的当前行”以使用两行,而不是矩阵以减少内存存储。
性能很大程度上取决于项目的数量、更改以及 equal
函数的复杂性。
Wagner–Fischer 算法
具有复杂度 O(mn),因此应该用于小于 100 项的集合。
Heckel
当前版本的 DeepDiff
采用了 Heckel 算法,具体见 文件之间差异隔离技术。它基于两条关于行出现次数和计数的观察。与第一版相比,结果略显冗长,但它的运行时间是线性的。
感谢
- 文件之间差异隔离 的详细解释
- HeckelDiff 使用追踪
deleteOffset
的巧妙方法来降低操作数
更多
还存在其他有趣的算法
基准测试
基准测试是在实际的 iPhone 6 设备上进行的,使用随机生成的包含连字符的 UUID 字符串(36个字符),以使比较更具挑战性。
在不同框架之间
以下是一些流行的比较框架进行比较
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)
})
结果
DeepDiff: 0.0450611114501953s
Differ: 0.199673891067505s
Dwifft: 149.603884935379s
Changeset: 77.5895738601685s
ListDiff: 0.105544805526733s
复杂性提升
这里是如何处理大量条目和变更的 DeepDiff
DeepDiff: 0.233131170272827s
DeepDiff: 0.453393936157227s
DeepDiff: 1.04128122329712s
Are you sure?
安装
CocoaPods
在您的 Podfile 中添加以下内容
pod 'DeepDiff'
Carthage
在您的 Cartfile 中添加以下内容
github "onmyway133/DeepDiff"
Swift 包管理器
在您的 Package.swift 文件中添加以下内容
.package(url: "https://github.com/onmyway133/DeepDiff.git", .upToNextMajor(from: "2.3.0"))
DeepDiff 也可以手动安装。只需下载并将 Sources
文件夹拖放到您的项目中。
作者
Khoa Pham, [email protected]
贡献力量
我们非常欢迎您为 DeepDiff 贡献,更多信息请查看 CONTRIBUTING 文件。
许可证
DeepDiff 以 MIT 许可证可用。更多信息请查看 LICENSE 文件。