【问题标题】:Swift Array diffSwift 数组差异
【发布时间】:2016-05-02 06:02:51
【问题描述】:

给定两个数组,其中一个是旧值集,另一个是新值,我想找到这两个数组的“差异”,这样对原始数组的更新可以表示为:

enum CollectionChange<T: SequenceType> {
    case Initial(T)
    case Update(T, deletions: [Int], insertions: [Int], modifications: [Int]) 
}

我正在尝试构建一个更简单的this 版本,其中更改对象是基于对象相等性构建的,而不是像RAC-MutableCollectionProperty 那样的索引(代码是here,这可能是最一段时间以来,我看到了一段复杂的代码;没有文档也无济于事)。

对于这个项目来说,同样重要的是能够以任何粒度级别观察数组的变化。例如,将T 限制为Equatable 的一维数组是一个相对简单的用例。您可以像RAC-MutableCollectionProperty 那样建立某种描述更改的表,检查对象是否相等。然而,一旦你开始使用二维数组并且更深入,它会变得有点棘手,因为你不仅必须区分最低级别的元素,而且还要描述部分级别的删除。在实践中,实际上并不需要超过 2D 数组,但如果有一个无论数组深度如何都可以工作的解决方案,那就太好了。我不一定要寻找解决方案(尽管那太棒了),实际上只是有关如何解决此问题的任何指针和高级解决方案。

我想到的观察多个数组级别的一种方法是编写一个适用于一维数组的 diffing 函数并构造一个如下属性:

let property: MutableCollectionProperty<MutableCollectionProperty<Int>>

属性将检查其泛型类型是否属于它自己的类型。我必须将更改描述更改为更接近

enum Changes<T> {
    case Initial(T)
    case Update(T, deletions: [NSIndexPath], insertions: [NSIndexPath], modifications: [NSIndexPath])
}

或者类似的东西

enum Changes<T> {
    case Initial(T)
    case UpdateSections(sections: [T], deletions:[Int], insertions: [Int], modifications: [Int])
    case UpdateIndexes(T, deletions: [Int], insertions: [Int], modifications: [Int])
}

这些只是我的初步想法,我愿意接受任何解决方案或建议。

赏金编辑:

赏金将授予能够提供给出以下参数的解决方案的人:

  • 设x和y为两个swift数组
  • T: Equatable 类型的两个数组
  • 两个数组都可以是任意深度
  • x 的深度 == y 的深度

可以在变更集描述的地方生成变更集:

  • 哪些元素已从 x 中删除 到 y(按索引)
  • 哪些元素已插入到 y 中 不在 x 中(按索引)
  • 从 x 开始移动了哪些元素 到 y(按索引)

只需在数组的最低级别描述更改(无需担心插入和删除更高的段,尽管您真的会因此获得 300 个代表)但是更改索引必须指明嵌套的索引路径。

例如,如果数组是一个 3d 数组,并且 array[0][5][2] 处的对象被删除,则结果索引更改应该是数组 [0, 5, 2]。该数组描述了一次删除,所有删除的类型都是[[Int]]

编辑:

我取消了对任何深度的数组的要求。假设它们只是一维数组。

【问题讨论】:

  • 对于邮件格式的道歉,我将使用格式正确的邮件更新我的原始帖子。
  • 你可能对github.com/jflinter/Dwifft感兴趣
  • 我会看看(可能最终会使用它),但我也有兴趣知道如何自己做。
  • 不是一个打包的解决方案,但@DaveDeLong 不久前有一些关于这个主题的useful notes

标签: ios arrays swift


【解决方案1】:

我不确定这是否满足您的所有赏金要求,但我会发布一些用于计算数组差异的代码:

func arrayInsertionDeletionAndNoopIndexes<T: Equatable>(objects: [T], originalObjects: [T]) -> ([Int], [Int], [Int]) {
    let insertions = objects.filter({ !originalObjects.contains($0) }).map({ objects.index(of: $0)! })
    let noops = originalObjects.filter({ objects.contains($0) }).map({ originalObjects.index(of: $0)! })
    let deletions = originalObjects.filter({ !objects.contains($0) }).map({ originalObjects.index(of: $0)! })

    return (insertions, deletions, noops)
}

func arrayInsertionDeletionAndNoopIndexPaths<T: Equatable>(objects: [T], originalObjects: [T], section: Int = 0) -> ([IndexPath], [IndexPath], [IndexPath]) {
    let (insertions, deletions, noops) = arrayInsertionDeletionAndNoopIndexes(objects: objects, originalObjects: originalObjects)

    let insertionIndexPaths = insertions.map({ IndexPath(row: $0, section: section) })
    let deletionIndexPaths = deletions.map({ IndexPath(row: $0, section: section) })
    let noopIndexPaths = noops.map({ IndexPath(row: $0, section: section) })

    return (insertionIndexPaths, deletionIndexPaths, noopIndexPaths)
}

我的具体用例是计算差异以更新UITableView,为此我还有以下用途:

extension UITableView {

    func insertAndDeleteCellsForObjects<T: Equatable>(objects: [T], originalObjects: [T], section: Int = 0) {
        let (insertions, deletions, _) = arrayInsertionDeletionAndNoopIndexPaths(objects: objects, originalObjects: originalObjects, section: section)

        if insertions.count > 0 || deletions.count > 0 {
            beginUpdates()
            insertRows(at: insertions, with: .automatic)
            deleteRows(at: deletions, with: .automatic)
            endUpdates()
        }
    }

}

【讨论】:

    【解决方案2】:

    从 Swift 2.2 开始,这是不可能的。 您提出以下要求:

    • T: Equatable 类型的两个数组
    • 两个数组都可以是任意深度

    但是使受约束的扩展符合新协议的能力只有planned for Swift 3.0,所以现在你不能使extension Array where Element: Array&lt;Equatable&gt; 符合Equatable 协议。这意味着只有一维数组的类型可以是T: Equatable

    编辑:

    基本上你需要做的是写一个算法来解决Longest common subsequence problem。对于一维数组,您可以使用Dwifft 库,它通过以下方式解决问题:

    public extension Array where Element: Equatable {
        public func diff(other: [Element]) -> Diff<Element> {
            let table = MemoizedSequenceComparison.buildTable(self, other, self.count, other.count)
            return Array.diffFromIndices(table, self, other, self.count, other.count)
        }
    
        private static func diffFromIndices(table: [[Int]], _ x: [Element], _ y: [Element], _ i: Int, _ j: Int) -> Diff<Element> {
            if i == 0 && j == 0 {
                return Diff<Element>(results: [])
            } else if i == 0 {
                return diffFromIndices(table, x, y, i, j-1) + DiffStep.Insert(j-1, y[j-1])
            } else if j == 0 {
                return diffFromIndices(table, x, y, i - 1, j) + DiffStep.Delete(i-1, x[i-1])
            } else if table[i][j] == table[i][j-1] {
                return diffFromIndices(table, x, y, i, j-1) + DiffStep.Insert(j-1, y[j-1])
            } else if table[i][j] == table[i-1][j] {
                return diffFromIndices(table, x, y, i - 1, j) + DiffStep.Delete(i-1, x[i-1])
            } else {
                return diffFromIndices(table, x, y, i-1, j-1)
            }
        }
    }
    
    internal struct MemoizedSequenceComparison<T: Equatable> {
        static func buildTable(x: [T], _ y: [T], _ n: Int, _ m: Int) -> [[Int]] {
            var table = Array(count: n + 1, repeatedValue: Array(count: m + 1, repeatedValue: 0))
            for i in 0...n {
                for j in 0...m {
                    if (i == 0 || j == 0) {
                        table[i][j] = 0
                    }
                    else if x[i-1] == y[j-1] {
                        table[i][j] = table[i-1][j-1] + 1
                    } else {
                        table[i][j] = max(table[i-1][j], table[i][j-1])
                    }
                }
            }
            return table
        }
    }
    

    【讨论】:

      【解决方案3】:

      如果你只需要计算两个数组之间的差异,这里有一个基于shawkinaw答案的替代实现:

      typealias Insertions = [Int]
      typealias Deletions = [Int]
      typealias ChangeSet = (Insertions, Deletions)
      
      func Diff<T: Equatable>(objects: [T], originalObjects: [T]) -> ChangeSet {
          guard objects.count > 0 && originalObjects.count > 0 else { return ChangeSet([], []) }
      
          let insertedObjects = objects.filter({ !originalObjects.contains($0) })
          let insertionIndicies = insertedObjects.compactMap({ objects.index(of: $0) })
      
          let deletedObjects = originalObjects.filter({ !objects.contains($0) })
          let deletionIndicies = deletedObjects.compactMap({ originalObjects.index(of: $0) })
      
          return ChangeSet(insertionIndicies, deletionIndicies)
      }
      

      insertionIndiciesInt 类型的数组。数组中的每个Int 指的是originalObjects 数组需要插入objects 数组中的项的索引。

      deletionIndiciesInt 类型的数组。数组中的每个Int 指的是originalObjects 数组必须删除项目的索引。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-08-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-08-05
        • 2012-08-28
        相关资源
        最近更新 更多