Swift数组差异

时间:2016-05-02 06:02:51

标签: ios arrays swift

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

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

我正在尝试构建一个更简单的this版本,其中基于对象相等性构建更改对象,而不是RAC-MutableCollectionProperty的索引(代码为here)什么可能是我在一段时间内看到的最复杂的代码;没有文档没有帮助。)

此项目的另一个重要功能是能够以任何粒度级别观察阵列的更改。例如,限制TEquatable的一维数组是一个相对简单的用例。您可以,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])
}

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

BOUNTY EDIT:

奖金将颁发给能够提供以下参数的解决方案的人:

  • 让x和y为两个快速数组
  • 类型T: Equatable
  • 的两个数组
  • 两个数组都可以是任何深度
  • x的深度= y的深度

可以生成更改集,其中更改集描述:

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

只需要在阵列的最低级别描述更改(不需要担心插入和删除更高的段,尽管你真的获得300个代表)但是更改索引必须指明嵌套索引路径。

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

编辑:

我正在删除任何深度阵列的要求。让我们说它们只是一维阵列。

3 个答案:

答案 0 :(得分:10)

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

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()
        }
    }

}

答案 1 :(得分:7)

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

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

但是,使约束扩展符合新协议的能力仅为planned for Swift 3.0,因此您现在无法使extension Array where Element: Array<Equatable>符合Equatable协议。这意味着只有1d数组可以是T: Equatable类型。

修改

基本上你需要做的是编写一个解算Longest common subsequence problem的算法。对于1d数组,您可以使用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
    }
}

答案 2 :(得分:0)

如果只需要计算两个数组之间的差,这是基于info.plist答案的替代实现:

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) } 是类型insertionIndicies的数组。数组中的每个Int都是Int数组需要插入originalObjects数组中的项目的标记。

objects是类型deletionIndicies的数组。数组中的每个Int均指Int数组必须删除项目的标记。