Compare arrays in swift
Solution 1:
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:
==
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:
- I think the performance is optimal for doing value (not reference) comparisons
- Yes, if you want to compare values
- 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:
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:
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 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 meanArray
orOptional
conformed toEquatable
. 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.