Compare arrays in swift

ArraysSwiftComparison

Arrays Problem Overview


Trying to understand how swift compares arrays.

var myArray1 : [String] = ["1","2","3","4","5"]
var myArray2 : [String] = ["1","2","3","4","5"]

// 1) Comparing 2 simple arrays

if(myArray1 == myArray2) {
    println("Equality")
} else {
    println("Equality no")
}
// -> prints equality -> thanks god

// 2) comparing to a "copy" of an array

// swift copies arrays when passed as parameters (as per doc)
func arrayTest(anArray: [String]) -> Bool {
    return anArray == myArray1
}

println("Array test 1 is \(arrayTest(myArray1))")
println("Array test 2 is \(arrayTest(myArray2))")
// equality works for both

myArray2.append("test")
println("Array test 2 is \(arrayTest(myArray2))")
// false (obviously)

myArray2.removeAtIndex(5)
println("Array test 2 is \(arrayTest(myArray2))")
// true

Apple says there are optimisations behind the scene on array copies. Looks like sometimes - not always - structures are actually copied or not.

That said,

  1. is == iterating over all the array to perform a element-based comparison ? (looks like it) -> How about performance / memory usage on very large arrays then ?

  2. Are we sure == will ever return true if all elements are equal ? I have bad memories of == on Java Strings

  3. Is there a way to check if myArray1 and myArray2 are technically using the same "memory location" / pointer / etc. ? i'm after understanding how the optimisation works and potential caveats.

Thanks.

Arrays Solutions


Solution 1 - Arrays

You’re right to be slightly nervous about ==:

struct NeverEqual: Equatable { }
func ==(lhs: NeverEqual, rhs: NeverEqual)->Bool { return false }
let x = [NeverEqual()]
var y = x
x == y  // this returns true

[NeverEqual()] == [NeverEqual()] // false
x == [NeverEqual()] // false

let z = [NeverEqual()]
x == z // false

x == y // true

y[0] = NeverEqual()
x == y // now false

Why? Swift arrays do not conform to Equatable, but they do have an == operator, defined in the standard library as:

func ==<T : Equatable>(lhs: [T], rhs: [T]) -> Bool

This operator loops over the elements in lhs and rhs, comparing the values at each position. It does not do a bitwise compare – it calls the == operator on each pair of elements. That means if you write a custom == for your element, it’ll get called.

But it contains an optimization – if the underlying buffers for the two arrays are the same, it doesn’t bother, it just returns true (they contain identical elements, of course they’re equal!).

This issue is entirely the fault of the NeverEqual equality operator. Equality should be transitive, symmetric and reflexive, and this one isn't reflexive (x == x is false). But it could still catch you unawares.

Swift arrays are copy-on-write – so when you write var x = y it doesn’t actually make a copy of the array, it just points x’s storage buffer pointer at y’s. Only if x or y are mutated later does it then make a copy of the buffer, so that the unchanged variable is unaffected. This is critical for arrays to behave like value types but still be performant.

In early versions of Swift, you actually could call === on arrays (also in early versions, the mutating behaviour was a bit different, if you mutated x, y would also change even though it had been declared with let – which freaked people out so they changed it).

You can kinda reproduce the old behaviour of === on arrays with this (very implementation-dependent not to be relied-on except for poking and prodding investigations) trick:

let a = [1,2,3]
var b = a

a.withUnsafeBufferPointer { outer in 
    b.withUnsafeBufferPointer { inner in 
        println(inner.baseAddress == outer.baseAddress) 
    } 
}

Solution 2 - Arrays

== in Swift is the same as Java's equals(), it compares values.

=== in Swift is the same as Java's ==, it compares references.

In Swift you can compare array content values as easy as this:

["1", "2"] == ["1", "2"]

But this will not work if you want to compare references:

var myArray1 = [NSString(string: "1")]
var myArray2 = [NSString(string: "1")]

myArray1[0] === myArray2[0] // false
myArray1[0] == myArray2[0] // true

So the answers:

  1. I think the performance is optimal for doing value (not reference) comparisons
  2. Yes, if you want to compare values
  3. Swift arrays are value type and not reference type. So the memory location is the same only if you compare it to itself (or use unsafe pointers)

Solution 3 - Arrays

It depends on how do you want to compare. For example:

["1", "2"] == ["1", "2"] // true

but

["1", "2"] == ["2", "1"] // false

If you need that second case to also be true and are ok with ignoring repetitive values, you can do:

Set(["1", "2"]) == Set(["2", "1"]) // true

(use NSSet for Swift 2)

Solution 4 - Arrays

For compare arrays of custom objects we can use elementsEqual.

class Person {
    
    let ID: Int!
    let name: String!
    
    init(ID: Int, name: String) {
        
        self.ID = ID
        self.name = name
    }
}

let oldFolks = [Person(ID: 1, name: "Ann"), Person(ID: 2, name: "Tony")]
let newFolks = [Person(ID: 2, name: "Tony"), Person(ID: 4, name: "Alice")]

if oldFolks.elementsEqual(newFolks, by: { $0.ID == $1.ID }) {
    
    print("Same people in same order")
    
} else {
    
    print("Nope")
}

Solution 5 - Arrays

Arrays conform to Equatable in Swift 4.1, negating the caveats mentioned in previous answers. This is available in Xcode 9.3.

https://swift.org/blog/conditional-conformance/

> But just because they implemented == did not mean Array or Optional conformed to Equatable. Since these types can store non-equatable types, we needed to be able to express that they are equatable only when storing an equatable type.

> This meant these == operators had a big limitation: they couldn’t be used two levels deep.

>With conditional conformance, we can now fix this. It allows us to write that these types conform to Equatable—using the already-defined == operator—if the types they are based on are equatable.

Solution 6 - Arrays

If you have an array of custom objects, one has to be careful with the equality test, at least with Swift 4.1:
If the custom object is not a subclass of NSObject, the comparison uses the static func == (lhs: Nsobject, rhs: Nsobject) -> Bool, which has to be defined.
If it is a subclass of NSObject, it uses the func isEqual(_ object: Any?) -> Bool, which has to be overridden.

Please check the following code, and set breakpoints at all return statements.

class Object: Equatable {
	
	static func == (lhs: Object, rhs: Object) -> Bool {
		return true
	}
}

The following class inheritates Equatable from NSObject

class Nsobject: NSObject {
	
	static func == (lhs: Nsobject, rhs: Nsobject) -> Bool {
		return true
	}

	
	override func isEqual(_ object: Any?) -> Bool {
		return true
	}

}  

They can be compared with:

let nsObject1 = Nsobject()
let nsObject2 = Nsobject()
let nsObjectArray1 = [nsObject1]
let nsObjectArray2 = [nsObject2]
let _ = nsObjectArray1 == nsObjectArray2

let object1 = Object()
let object2 = Object()
let objectArray1 = [object1]
let objectArray2 = [object2]
let _ = objectArray1 == objectArray2

Solution 7 - Arrays

Whereas comparing arrays in swift, must be clear what are the comparison entities.

Ex: A = [1,2,3] B = [2,1,3]

enter image description here

  1. return A == B -> false The comparison is being made traversing both arrays and comparing its index's data.

  2. return Set(A) == Set(B) -> true Because of function Set(), the comparison is made by whole single value collection.

> "Set operations are not limited to use with other sets. Instead, you > can perform set operations with another set, an array, or any other > sequence type." >
> Use the “equal to” operator (==) to test whether two sets contain > the same elements. >https://developer.apple.com/documentation/swift/set

Can find more here: https://docs.swift.org/swift-book/LanguageGuide/CollectionTypes.html

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionvivien.destpernView Question on Stackoverflow
Solution 1 - ArraysAirspeed VelocityView Answer on Stackoverflow
Solution 2 - ArraysKirsteinsView Answer on Stackoverflow
Solution 3 - ArraysdemostenView Answer on Stackoverflow
Solution 4 - Arraystier777View Answer on Stackoverflow
Solution 5 - ArraysGreg McCoyView Answer on Stackoverflow
Solution 6 - ArraysReinhard MännerView Answer on Stackoverflow
Solution 7 - ArraysWander FilhoView Answer on Stackoverflow