Swift error comparing two arrays of optionals
The issue here is the distinction between something having an ==
operator, versus something being “equatable”.
Both Optional
and Array
have an ==
operator, that works when what they contain is equatable:
// if T is equatable, you can compare each entry for equality
func ==<T : Equatable>(lhs: [T], rhs: [T]) -> Bool
// if T is equatable, you can compare the contents, if any, for equality
func ==<T : Equatable>(lhs: T?, rhs: T?) -> Bool
let i: Int? = 1
let j: Int = 1
i == j // fine, Int is Equatable
["a","b"] == ["a","b"] // and so is String
But they themselves do not conform to Equatable
. This makes sense given you can put a non-equatable type inside them. But the upshot of this is, if an array contains a non-equatable type, then ==
won’t work. And since optionals aren’t Equatable
, this is the case when you put an optional in an array.
You'd get the same thing if you tried to compare an array of arrays:
let a = [[1,2]]
let b = [[1,2]]
a == b // error: `==` can’t be applied to `[Array<Int>]`
If you wanted to special case it, you could write ==
for arrays of optionals as:
func ==<T: Equatable>(lhs: [T?], rhs: [T?]) -> Bool {
if lhs.count != rhs.count { return false }
for (l,r) in zip(lhs,rhs) {
if l != r { return false }
}
return true
}
For a counter-example, since Set
requires its contents to be hashable (and thus equatable), it can be equatable:
let setarray: [Set<Int>] = [[1,2,3],[4,5,6]]
setarray == [[1,2,3],[4,5,6]] // true
Comparing array of force unwrapped optionals
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
But neither ImplicitlyUnwrappedOptional<Wrapped>
nor Optional<Wrapped>
conform to Equatable
, even if the
underlying type Wrapped
does.
Possible options are (assuming that VMVideo
conforms to Equatable
):
Change your code so that
videos
andshuffledVideos
are[VMVideo]
arrays instead of[VMVideo!]
.Compare the arrays elementwise:
XCTAssert(videos.count == shuffledVideos.count
&& !zip(videos, shuffledVideos).contains {$0 != $1 })Define a
==
operator for arrays of implicitly unwrapped equatable
elements:func ==<Element : Equatable> (lhs: [Element!], rhs: [Element!]) -> Bool {
return lhs.count == rhs.count && !zip(lhs, rhs).contains {$0 != $1 }
}
Inscrutable Swift compiler error when comparing two object arrays
I think that your problem is that you have not made MyObectModel
equatable.
In order to check equality of two arrays of MyObjectModel
you need to be able to check equality of two MyObjectModel
objects.
To do this you need to do the following...
extension MyObjectModel: Equatable {}
// as a top level function
func ==(lhs: MyObjectModel, rhs: MyObjectModel) -> Bool {
// check if your objects are equal here...
return lhs.name == rhs.name
}
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.
Why can't Swift's greater-than or less-than operators compare optionals when the equality operators can?
It makes perfect sense for the equality operator to support optionals, because it's absolutely clear that for any integer valued variable i
:
nil == nil
nil != i
i != nil
i == i
if and only if their values are the same
On the other hand, it's not clear how comparison to nil
should act:
Is i
less than nil
?
- If I want to sort an array so that all the
nil
s come out at the end, then I would wanti
to be less thannil
. - But if I want to sort an array so that all the
nil
s come out at the start, then I would wanti
to be greater thannil
.
Since either of these are equally valid, it wouldn't make sense for the standard library to favor one over the other. It's left to the programmer to implement whichever comparison makes sense for their use-case.
Here's a toy implementation that generates a comparison operator to suite either case:
func nilComparator<T: Comparable>(nilIsLess: Bool) -> (T?, T?) -> Bool {
return {
switch ($0, $1) {
case (nil, nil): return false
case (nil, _?): return nilIsLess
case (_?, nil): return !nilIsLess
case let (a?, b?): return a < b
}
}
}
let input = (0...10).enumerated().map {
$0.offset.isMultiple(of: 2) ? Optional($0.element) : nil
}
func concisePrint<T>(_ optionals: [T?]) -> String {
return "[" + optionals.map { $0.map{ "\($0)?" } ?? "nil" }.joined(separator: ", ") + "]"
}
print("Input:", concisePrint(input))
print("nil is less:", concisePrint(input.sorted(by: nilComparator(nilIsLess: true))))
print("nil is more:", concisePrint(input.sorted(by: nilComparator(nilIsLess: false))))
Output:
Input:
[0?, nil, 2?, nil, 4?, nil, 6?, nil, 8?, nil, 10?]
nil is less:
[nil, nil, nil, nil, nil, 0?, 2?, 4?, 6?, 8?, 10?]
nil is more:
[0?, 2?, 4?, 6?, 8?, 10?, nil, nil, nil, nil, nil]
How to compare two array of objects?
You can try like this:
let result = zip(array1, array2).enumerated().filter() {
$1.0 == $1.1
}.map{$0.0}
How do you compare 2 operands of type Date? to sort an array in Swift?
Optionals cannot be compared directly (compare SE-0121 – Remove Optional Comparison Operators). But you can use the nil-coalescing operator ??
to provide a default date for entries without creation date:
Images.sorted(by: {$0.create_dt ?? .distantPast > $1.create_dt ?? .distantPast })
With .distantPast
the entries without creation date are sorted to the end of the list. With .distantFuture
they would be sorted to the start of the list.
how to compare two optional NSArrays in Swift
It is quite simple:
func isArrayEqualToArray(array1: NSArray?, array2: NSArray?) -> Bool {
return array1 == array2
}
does exactly what you want.
Why does it work? Here ==
is the operator that compares optionals
func ==<T : Equatable>(lhs: T?, rhs: T?) -> Bool
and that gives true
if both operands are nil
, or of both
operands are non-nil and the unwrapped operands are equal.
Also NSArray
inherits from NSObject
which conforms to Equatable
,
and comparing NSObject
s with ==
uses the isEqual:
method, which is
implemented as isEqualToArray:
on NSArray
.
Therefore
array1 == array2
gives the same result as
array1.isEqualToArray(array2)
Related Topics
Button State Activates on Wrong Cells
Tvos Textfield Transparent Background
Swift: Generic Overloads, Definition of "More Specialized"
Creating a Subclass of Skshapenode
How to Prompt for Accessibility Features in a MACos App (From the Appdelegate)
How to Wait for Http Requests to Finish
Swift Realm: After Writing Transaction Reference Set to Nil
Saving Highscores with Nsuserdefaults
Drag a Cgrect Using Uipangesturerecognizer
Inline Kvo of a Property in Another View Controller
Table View Cellforrowatindexpath Warning
"Message from Debugger: Unable to Attach" When Running Tests on Osx App
How Does Appdelegate.Swift Replace Appdelegate.H and Appdelegate.M in Xcode 6.3
Update Nstouchbar on the Fly to Add/Remove Items Programmatically
Swiftui Behavior of .Frame(Height: Nil)