DeepDiff 2.3.1

DeepDiff 2.3.1

Khoa Pham维护。



DeepDiff 2.3.1

DeepDiff

CI Status Version Carthage Compatible License Platform Swift

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
}

一些基本类型,如 StringIntCharacter,已经符合 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 算法,具体见 文件之间差异隔离技术。它基于两条关于行出现次数和计数的观察。与第一版相比,结果略显冗长,但它的运行时间是线性的。

感谢

更多

还存在其他有趣的算法

基准测试

基准测试是在实际的 iPhone 6 设备上进行的,使用随机生成的包含连字符的 UUID 字符串(36个字符),以使比较更具挑战性。

您可以查看代码 基准测试。测试灵感来源于 DiffUtil

在不同框架之间

以下是一些流行的比较框架进行比较

💪从 2000 项到 2100 项(100 删除,200 插入)

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

💪从 10000 个项目到 11000 个项目(1000 个删除,2000 个插入)

DeepDiff: 0.233131170272827s

💪从 20000 个项目到 22000 个项目(2000 个删除,4000 个插入)

DeepDiff: 0.453393936157227s

💪从 50000 个项目到 55000 个项目(5000 个删除,10000 个插入)

DeepDiff: 1.04128122329712s

💪从 100000 个项目到 1000000 个项目

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 文件。