Given two arrays, where one is the old set of values and the other is the new values, I want to find the "diff" of those two arrays such updates to the original array can be represented as:
enum CollectionChange<T: SequenceType> {
case Initial(T)
case Update(T, deletions: [Int], insertions: [Int], modifications: [Int])
}
I'm trying to build a simpler version of this where the changes object is built based on object equality, instead of indexes as RAC-MutableCollectionProperty
is (for which the code is here and what might be the most complicated bit of code I've seen in a while; no documentation doesn't help).
Also important for this project is the ability to be able to observe changes to an array at any level of granularity. For example, a one-dimensional array, restricting T
to Equatable
, is a relatively easy use case. You can, as RAC-MutableCollectionProperty
build up some sort of table that describes the changes, checking for equality on the objects. However once you get down to using two-dimensional arrays and deeper it gets a bit trickier because not only do you have to diff the elements at the lowest level but also describe section-level removals. In practice, no more than 2D arrays is really ever necessary but it'd be nice to have a solution that works regardless of the array depth. I'm not necessarily looking for a solution (although that'd be fantastic), really just any pointers and high level solutions on how to approach this problem.
One way I've thought of to observe multiple array levels is to write a diffing function that works on single dimensional arrays and construct a property such that:
let property: MutableCollectionProperty<MutableCollectionProperty<Int>>
where the property would check if its generic type is of it's own type. I'd have to change the changes description to something closer to
enum Changes<T> {
case Initial(T)
case Update(T, deletions: [NSIndexPath], insertions: [NSIndexPath], modifications: [NSIndexPath])
}
or maybe something like
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])
}
These are just my preliminary thoughts though, I'm open to any solution or suggestion.
BOUNTY EDIT:
The bounty will be awarded to someone who can provide a solution that given the following parameters:
T: Equatable
a change set can be generated where a change set describes:
Changes only have to be described at the lowest level of the array (no need to worry about insertion & removal of higher segments, although you'd really earn the 300 rep with that) but change indexes must indicate the nested index path.
For example, if the array is a 3d array and an object at array[0][5][2]
was deleted, the resulting index change should be an array [0, 5, 2]
. That array describes a single deletion and all the deletions would be of type [[Int]]
.
Edit:
I'm removing the requirement of the arrays being of any depth. Let's say that they're simply 1d arrays.
To check if two arrays are equal in Swift, use Equal To == operator. Equal To operator returns a Boolean value indicating whether two arrays contain the same elements in the same order. If two arrays are equal, then the operator returns true , or else it returns false .
Difference between an Array and Set in SwiftArray is faster than set in terms of initialization. Set is slower than an array in terms of initialization because it uses a hash process. The array allows storing duplicate elements in it. Set doesn't allow you to store duplicate elements in it.
A set is unordered and each element can only appear once in a set. While an array can contain duplicate elements, each value contained in a set is unique.
I'm not sure this meets all your bounty requirements, but I'll post some code I use for computing arrays differences:
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)
}
My specific use case is for computing differences to update a UITableView
, for which purpose I also have the following:
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()
}
}
}
As of Swift 2.2, this is impossible. You give the following requirements:
T: Equatable
But the ability to make a constrained extension conform to a new protocol is only planned for Swift 3.0, so right now you can't make extension Array where Element: Array<Equatable>
conform to Equatable
protocol. This means that only 1d arrays can be of of type T: Equatable
.
EDIT:
Basically what you need to do is to write an algorithm that solves Longest common subsequence problem. For 1d arrays you can use Dwifft library which solves the problem in the following way:
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
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With