Swift Array diff - ios

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:
Let x and y be two swift array
both arrays of type T: Equatable
both arrays can be of any depth
the depth of x == the depth of y
a change set can be generated where a change set describes:
which elements have been deleted from the x
to y (by index)
which elements have been inserted into y that
weren't in x (by index)
which elements have been moved going from x
to y (by index)
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.

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:
both arrays of type T: Equatable
both arrays can be of any depth
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 only need to compute the difference between two arrays, here's an alternative implementation based on shawkinaw answer:
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)
}
The insertionIndicies is an array of type Int. Each Int in the array refers to the indicies where the originalObjects array need to insert items from the objects array.
The deletionIndicies is an array of type Int. Each Int in the array refers to the indicies where the originalObjects array has to delete items.

Related

Find lowest value and its index number in swift

I have the following array
var numbers = [2, 4, 4, 2, 3, 1]
The sequence is very important and I need to find the index of the lowest value.
So how can I find the lowest value index? I need to be able to call numbers[lowestValueIndex] and get the correct value
Any help is appreciated
I always think it gives a cleaner call site to extend the array in this type of scenario.
extension Array where Element == Int {
func lowest() -> (value: Element, positions:[Index])? {
guard !isEmpty else {return nil } //you may wish to throw an error rather than return nil
return indices.reduce( (value: Element.max, positions: [Index]() ) ) {
switch self[$1] {
case let x where x < $0.value: return (value: self[$1], positions:[$1])
case let x where x > $0.value: return $0
default: return ($0.value, $0.positions + [$1])
}
}
}
}
The switch statements can be simplified using an _ or pattern matching, but I feel this more verbose approach is easier to understand. Throwing an error or returning a Result may be nicer way of dealing with an empty array than an optional return type, but would add bloat to the answer and can be added later by the OP if preferred.
[2,3,6,1,7,3,1,6,7].lowest() // (value: 1, positions: [3, 6])
[2,3,6,6,7,3,1,6,7].lowest() // (value: 1, positions: [6])
[Int]().lowest() // nil
You can use the min(by:) array method; The trick is to operate on the array's indices property so that you can return the relevant index:
func minIndex(someArray: [Int]) -> Int? {
return someArray.indices.min { someArray[$0] < someArray[$1] }
}
The function will return nil in the case where the array is empty.
For simplicity I have shown this as a function. You could, of course, implement this as an extension on Array if you desired.
The other answers handle what to do if you only need one value. Otherwise…
let numbers = [2, 4, 4, 2, 3, 1, 1]
// [(offset 5, element 1), (offset 6, element 1)]
numbers.min().map { min in
numbers.enumerated().filter { $0.element == min }
}
And if you're going to need the array sorted for further usage, prefix is better than filter.
let sorted =
[2, 4, 4, 2, 3, 1, 1]
.enumerated()
.sorted(by: \.element)
sorted.first.map { first in
sorted.prefix { $0.element == first.element }
}
public extension Sequence {
/// Sorted by a common `Comparable` value.
func sorted<Comparable: Swift.Comparable>(
by comparable: (Element) throws -> Comparable
) rethrows -> [Element] {
try sorted(by: comparable, <)
}
/// Sorted by a common `Comparable` value, and sorting closure.
func sorted<Comparable: Swift.Comparable>(
by comparable: (Element) throws -> Comparable,
_ areInIncreasingOrder: (Comparable, Comparable) throws -> Bool
) rethrows -> [Element] {
try sorted {
try areInIncreasingOrder(comparable($0), comparable($1))
}
}
}
First you need to define the behavior of repetitive value in the input array.
Once that is done, try this:
func returnLowestValueIndex(array: [Int]) -> Int? {
guard !array.isEmpty else { return nil }
var lowestValueIndex: Int = 0
for (index, value) in array.enumerated() {
if value < array[lowestValueIndex] {
lowestValueIndex = index
}
return lowestValueIndex
}
var numbers = [2, 4, 4, 2, 3, 10]
returnLowestValueIndex(array: numbers)

How to fix "Overlapping accesses to 'self', but modification requires exclusive access; consider copying to a local variable" error in my code?

I can't continue with my app and I can't test run it because something is wrong in my code that I don't know how to fix!
Here is the code:
import Foundation
extension Array {
mutating func shuffle() {
if count < 2 { return }
for i in 0..<(count - 1) {
let j = Int(arc4random_uniform(UInt32(count - i))) + i
customSwap(a: &self[i], b: &self[j])
}
}
}
func customSwap<T>(a: inout T, b: inout T) {
let temp = a
a = b
b = temp
}
The problem is that an array is a value type, and when you modify one element you change the whole array. So your call to customSwap() is passing in two references to the whole array which leads to the overlapping accesses to self error.
Instead, you could write customSwap() to take one copy of the array and the indices you want to swap:
func customSwap<T>(_ array: inout [T], _ a: Int, _ b: Int) {
let temp = array[a]
array[a] = array[b]
array[b] = temp
}
and then call it like this:
customSwap(&self, i, j)
But you don't have to do that, because Array has a built-in swapAt(_:_) defined like this:
mutating func swapAt(_ i: Int, _ j: Int)
So you could replace your customSwap call with:
self.swapAt(i, j)
But Array has a built-in shuffle() that you can just call instead of implementing it yourself.

Swift Filtering array elements

I am creating a function that will filter an array, e.g.
x = [10,20,30,40,50]
filter(x,10,20)
output should be 30,40,50.
I am getting an index out of bounds error .
Here's my code:
func filterArray( _ x: [Int], _ nums: Int...) -> [Int]{
var arrayX = x
for i in 0...arrayX.count-1{
for j in 0...nums.count-1 {
if arrayX[i] == nums[j]{//Changed arrayX to x because x was never changed
if let index = arrayX.index(of: nums[j]) {
arrayX.remove(at: index) //error is here
}
else{
}
}
}
}
return arrayX
}
var mArray = [10,20,30,40,50]
filterArray(mArray,10)
The way you are doing it is not correct, you are altering an array while looping through it. When you remove an object from the array, the array count changes but the loop still run using the previously calculated array.count value.
There is a much simpler way of doing this, you just need to combine filter and contains functions together for achieving this:
func filterArray( _ x: [Int], _ nums: Int...) -> [Int]
{
let filteredArray = x.filter({ !nums.contains($0)})
return filteredArray
}
once you remove one element from array its size change, but your loop is still going till previous count that is causing the issue try taking different array to go through the loop and for storing the result and you don't need to find the index its already there, value of 'i' is the index of the element.
You function can be faster if you use a Set.
func filter(list: [Int], _ remove: Int...) -> [Int] {
let removeSet = Set(remove)
return list.filter { removeSet.contains($0) }
}

How to add numbers in a string array? Swift

i have an array, var hoursPlayed = String
they are in a tableView and are all numbers, how would i add the numbers in that array together to get the average of hours played????? in Swift 2
You could use reduce:
let sum= hoursPlayed.reduce(0.0,combine:{$0+Float($1)!})
Basically you are iterating through the array and accumulating all the values. Since it is an array of strings,for simplicity I've force unwrapped to a Float, but you must check for the optional. The reduce function takes a closure as argument with 2 parameters. The dollar sign means take the first and the second and sum them.
Now you can easily divide to the number of elements in the array to have an avergae.
If you are in objC world it would be nice use key value coding and the #avg operator.
[UPDATE]
As Darko posted out the first version won't compile. The error was converting the first argument to a Float, since reduce takes an initial value and I put it as Float there is no need for further conversion.
let array = ["10.0", "30.0"]
if array.count > 0 {
let average = array.reduce(0.0, combine: {$0 + (Double($1) ?? 0.0)}) / Double(array.count)
print(average) // 20.0
}
$0 does not need to be converted because it is guaranteed that it's always Double. $0 is inferred from the initial value, which is declared as 0.0: Double.
array.count has to be checked to guard against a division thru 0.
I'd use a combination of flatMap to convert the strings to Doubles and reduce to add them up:
let doubles = array.flatMap { Double($0) }
let average = doubles.reduce(0.0, combine:+) / Double(doubles.count)
Using flatMap protects you from entries in array that can't be converted to Double If you know they all convert you can simplify it to:
let average = array.map({ Double($0)! }) / Double(array.count)
One final option is to extend Array with an average function if that seems like something you'll be more generally using, and use it in combination with flatMap and/or map:
protocol ArithmeticType {
static func zero() -> Self
func +(lhs:Self, rhs:Self) -> Self
func -(lhs:Self, rhs:Self) -> Self
func /(lhs:Self, rhs:Self) -> Self
func *(lhs:Self, rhs:Self) -> Self
init(_ number:Int)
}
extension Double : ArithmeticType {
static func zero() -> Double {
return 0.0
}
}
extension Array where Element : ArithmeticType {
func average() -> Element {
return reduce(Element.zero(), combine:+) / Element(count)
}
}
let avg = array.flatMap { Double($0) }.average()
Modified Darko's approach, which take in account if String is convertible to Double, or not. For an empty array it returns 0.0
let array = ["10.0", "31.2", "unknown", ""]
func avg(arr: [String])->Double {
let arr = array.flatMap(Double.init)
var avg = 0.0
if arr.count > 0 {
avg = arr.reduce(0.0, combine: + ) / Double(arr.count)
}
return avg
}
let a = avg(array)
print(a) // 20.6

Cannot subscript a value of type [(String, Int)] with an index of type (String, Int) Swift 2

I am learning iOS development, and have recently come across an issue when attempting to manipulate an array of tuples.
I get the following error message:
Cannot subscript a value of type '[(String, Int)]' with an index of type '(String, Int)'
The code generating it is as follows:
justStrings.append(filteredRestraunts[i2].0)
The function as a whole is this:
func filterBySliderValue () -> [String] {
var filteredRestraunts: [(String, Int)]
for var i = 0; i < restraunts.count; i++ {
if restraunts[i].1 > Int(starSlider.value) {
filteredRestraunts.append(restraunts[i])
}
else {filteredRestraunts.append(("", 1))}
}
var justStrings: [String]
var i2 = 0
for i2 in filteredRestraunts {
justStrings.append(filteredRestraunts[i2].0)
}
return justStrings
}
This is the array restraunts:
var restraunts: [(String, Int)] = [("Dallas BBQ", 3), ("Chicken Express", 4), ("Starbucks", 5)]
Thanks in advance.
In
for i2 in filteredRestraunts {
justStrings.append(filteredRestraunts[i2].0)
}
i2 is not an index, but iterates over the array elements, i.e.
it is a (String, Int) tuple. What you probably meant is
for i2 in filteredRestraunts {
justStrings.append(i2.0)
}
Additional remarks:
The variable
var i2 = 0
is not used at all, i2 in the for-loop is a new variable whose scope is
restricted to the loop.
The variables filteredRestraunts and justStrings
are not initialized, so this should cause additional compiler errors.
Both loops can be replaced by a more functional approach using
filter and map:
let filteredRestraunts = restraunts.filter { $0.1 > Int(starSlider.value) }
let justStrings = filteredRestraunts.map { $0.0 }
Which of course could be combined to
let justStrings = restraunts.filter { $0.1 > Int(starSlider.value) }.map { $0.0 }

Resources