Swift Equality Operator on Nested Arrays

Swift equality operator on nested arrays

Update: Conditional conformance has been implemented in Swift 4.1. In particular:

The standard library types Optional, Array, and Dictionary now conform to the Equatable protocol when their element types conform to Equatable. ...

(from the Swift CHANGELOG).

Arbitrarily nested arrays of Equatable elements are Equatable now
and can be compared with ==. Your code

var x: [[Simple]] = [[Simple(message: "a")], [Simple(message: "b")]]
var y: [[Simple]] = [[Simple(message: "a")], [Simple(message: "b")]]
x == y

compiles in Xcode 9.3 if Simple is Equatable.


(Old answer:)
The reason is similar as in Why is Equatable not defined for optional arrays. Arrays can be compared with == if the element type is Equatable:

/// Returns true if these arrays contain the same elements.
public func ==<Element : Equatable>(lhs: [Element], rhs: [Element]) -> Bool

That's why

var a: [Simple] = [Simple(message: "a")]
var b: [Simple] = [Simple(message: "a")]
a == b // -> true

compiles.

But even for equatable types T, Array<T> does not conform to the Equatable protocol, compare Why can't I make Array conform to Equatable?. Therefore, in

var x: [[Simple]] = [[Simple(message: "a")], [Simple(message: "b")]]
var y: [[Simple]] = [[Simple(message: "a")], [Simple(message: "b")]]
x == y // -> ERROR! Binary operator '==' cannot be applied to two '[[Simple]]’ operands

x and y are arrays with the element type [Simple] which does
not conform to the Equatable protocol, and there is no
matching == operator.

You could define a generic == operator for simply nested arrays as

func ==<Element : Equatable> (lhs: [[Element]], rhs: [[Element]]) -> Bool {
return lhs.count == rhs.count && !zip(lhs, rhs).contains {$0 != $1 }
}

or more simply (as suggested by @kennytm):

func ==<Element : Equatable> (lhs: [[Element]], rhs: [[Element]]) -> Bool {
return lhs.elementsEqual(rhs, by: ==)
}

This makes x == y compile and work as expected. At present, there seems
to be no way to define a == operator on arbitrarily nested arrays.

How to compare nested collections in swift

Here's an equality operator that will compare any two nested dictionaries with the same type:

func ==<T: Equatable, K1: Hashable, K2: Hashable>(lhs: [K1: [K2: T]], rhs: [K1: [K2: T]]) -> Bool {
if lhs.count != rhs.count { return false }

for (key, lhsub) in lhs {
if let rhsub = rhs[key] {
if lhsub != rhsub {
return false
}
} else {
return false
}
}

return true
}

Creating nested arrays in swift

you could use array with type of AnyObject as below example.

let arr: [AnyObject]  = ["+", 5, ["-", 1, ["/", 34, 1]]]

So in order to get operand you can get at index 0. Let say you want operand division "/" as sample above you can go at this index

arr[2][2][0]

Swift - Binary Operator == cannot be applied to two [[Double]] operands

In this case there is nothing misleading.

Normally the == operator is defined for Equatable items. That allows two Double values to be compared to each other, e.g. 1.0 == 1.0.

Then we have a specific == operator defined on Arrays of Equatable items:

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

That means that you can compare any arrays with equatable items. However, the arrays themselves are not Equatable.

There is no such operator defined for nested arrays.

You would have to define:

public func ==<Element : Equatable>(lhs: [[Element]], rhs: [[Element]]) -> Bool {
...
}

How do I implement an operator for a class nested in a generic struct?

Nesting a class or struct in a generic type struct is now flagged as invalid by XCode6 Beta6

Sample Image

Why is Equatable not defined for optional arrays

Update: Conditional conformance has been implemented in Swift 4.1. Arrays and optionals of Equatable elements are themselves
Equatable now, and your code

let a: [Int]? = [1]
let b: [Int]? = nil
a == b

compiles and works as expected in Xcode 9.3. The workarounds are not
needed anymore.


(Old answer:)
Optionals can be compared only if the underlying wrapped type is equatable:

public func ==<T : Equatable>(lhs: T?, rhs: T?) -> Bool

Now Arrays can be compared if the element type is equatable:

/// Returns true if these arrays contain the same elements.
public func ==<Element : Equatable>(lhs: [Element], rhs: [Element]) -> Bool

but even for equatable types T, Array<T> does not conform to the Equatable protocol.

At present, this is not possible in Swift, see for example
Why can't I make Array conform to Equatable? for a discussion
in the Apple developer forum. This change with the implementation
of SE-0143 Conditional conformances
in Swift 4.

Your implementation looks correct, here is a possible different one
using switch/case with pattern matching:

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

switch (lhs, rhs) {
case let (l?, r?) : // shortcut for (.Some(l), .Some(r))
return l == r
case (.None, .None):
return true
default:
return false
}
}

Equality of arrays with reference and type members in Swift

I'm not 100% sure about this. Should you find some errors please let me know.

First of all this does not compile

var firstPerson = Person("John")
var secondPerson = Person("James")

and it should be

var firstPerson = Person(name: "John")
var secondPerson = Person(name: "James")

How will Swift compare these arrays?

firstArray and secondArray are inferred to be NSArray because you are putting different kind of values into them and your are not explicitly declaring them as [Any].

When you write firstArray == secondArray the code is bridged to Objective-C this way

firstArray.isEqualToArray(secondArray as [AnyObject])

The isEqualToArray method checks for equality every element.

[0]

"firstWord" == "firstWord"

is converted to

("firstWord" as NSString).isEqual("firstWord" as NSString)

and is true.

[1]

Both arrays reference the same Person object so it's true

[2]

12 == 12

is converted to

(12 as NSNumber).isEqual(NSNumber(int: 12)) 

which is true.

Recap

So the result of the array comparation is true.

What does happen if the arrays are declared as [Any]?

In this case there is no Objective-C bridging.
In Swift we can check equality of 2 arrays if the related type is Equatable. Since Any is NOT Equatable we'll get a compiler error.

firstArray == secondArray
Binary operator `==` cannot be applied to two [Any] operands

How can I extend Array so equality check of arrays of optionals are possible?

If you want to keep it as an extension to Array:

extension Array where Element : Equatable {
static func ==(lhs: [Element?], rhs: [Element?]) -> Bool {
if lhs.count != rhs.count {
return false
}
else {
for i in 0..<lhs.count {
if lhs[i] != rhs[i] { return false }
}
return true
}
}
}

let a: [String?] = ["John", nil]
let b: [String?] = ["John", nil]
let c: [String?] = ["Jack", nil]

print(a == b) // true
print(a == c) // false

The for loop here is more efficient for 2 reasons: (a) you don't have to construct a temporary array of tuples like with zip and (b) it returns false as soon as a non-match is found.

Binary operator '!=' cannot be applied to two '[[String]]' operands

As mentioned in the comments, Swift Arrays don't conform to Equatable so [[T]] != [[T]] does not work because it requires [T] to be Equatable. You could use elementsEqual(_:by:) instead, which allows comparing elements using a custom equality function, without being Equatable:

arrayOfArrays = arrayOfArrays.filter { !$0.elementsEqual(specificArray, by: ==) }

(Note: Thanks to SE-0143 "Conditional conformances", this workaround is no longer needed once Swift 4 is released.)



Related Topics



Leave a reply



Submit