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
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
Accessing an Enumeration Association Value in Swift
Whither Dispatch_Once in Swift 3
Express For Loops in Swift With Dynamic Range
Passing Lists from One Function to Another in Swift
Nsattributedstring, Change the Font Overall But Keep All Other Attributes
Fatal Error: Swapping a Location With Itself Is Not Supported With Swift 2.0
How to Modify the Background Color of a List in Swiftui
Selecting Global or Object Print Function
Inter-App Data Migration (Migrating Data to New App Version)
Swift - Sort Array of Objects With Multiple Criteria
How to Enumerate an Enum With String Type
Deinit Method Is Never Called - Swift Playground
How to Find String B Missing Characters Based on String a and Add Them to String B
How to Check If a String Contains Another String in Swift