Compare arrays in swift
Asked Answered
B

7

74

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.

Buchmanism answered 19/12, 2014 at 14:6 Comment(4)
Direct pointer comparison is ===Slit
Doesn't work. === says -> [String] does not conform to AnyObjectBuchmanism
@Slit === is only for classes, Array is a struct.Peloquin
In the module definition, array equality is defined as func ==<T : Equatable>(lhs: [T], rhs: [T]) -> Bool with the comment /// Returns true if these arrays contain the same elements.Bicipital
S
84

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) 
    } 
}
Scherer answered 20/12, 2014 at 13:34 Comment(4)
There's more to this than meets the eye. [1] == [1] returns true, but [[1]] == [[1]] returns false. Beware!Park
Ah yes, fun one that. It’s actually because of a bug in the Swift compiler (confirmed by the Swift team previously), and it shouldn't compile. It is not using the Array == operator. Can't be, because arrays are not equatable so doesn’t match the definition I gave in my post. Instead what’s happening is Swift is doing an implicit conversion of the array literals to a pointers, and then the == for UnsafePointer is being used. The bug is, this should not kick in for operators, only for function calls (it's only there to make calling of C functions that need to take a const array easier).Scherer
Just for the sake of being correct. Now [[1]] == [[1]] returns true (Swift 2.3)Reversal
Actually, arrays conform to Equatable when the types of their elements conform to Equatable. It wouldn't make sense for the == to be defined on arrays if they were never equatable. See developer.apple.com/documentation/swift/array/2949952Zacek
P
25

== 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)
Peloquin answered 19/12, 2014 at 14:34 Comment(5)
=== doesn't work. Apparently it used to. Any other way to do it ? Plus what if you call a func with a very large array ? Is this gonna copy the whole array as stated in the documentation ? Looks like overhead to me.Buchmanism
=== will only work for reference types. Value types always has only 1 reference to to them so === wouldn't makes sense for them. Arrays are "considered" to be copied like all structs, but for performance reason under the hood arrays are not copied unless the are being mutated.Peloquin
The reason myArray1 == myArray2 is that NSObject conforms to Equatable, calling -[equals:] to conduct the test.Bicipital
Keep in mind that == array comparison only works if array items are non-optional. For example, if let str1: String? = "abc", and let str2: String? = "abc", then [str1] == [str2] is false, but [str1!] == [str2!] is true.Gaynor
@BrianNickel If you had a chance, can you take a look at this question. It's about your comment. Kirsteins's answer seems a bit misleading :)Randyranee
A
14

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)

Aflcio answered 27/1, 2017 at 17:13 Comment(0)
I
13

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")
}
Ingalls answered 28/6, 2018 at 22:28 Comment(2)
+1 for using elementsEqual. To check whether two arrays are exactly equal, sort them before applying elementsEqual methodParsimonious
Isn't elementsEqual the same as the == operator in the form of a method?Homoiousian
P
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 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.

Padriac answered 27/1, 2018 at 14:49 Comment(0)
V
2

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

Vachell answered 16/3, 2022 at 0:42 Comment(0)
V
1

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
Vallee answered 15/7, 2018 at 14:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.