Swift Error Comparing Two Arrays of Optionals

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 and shuffledVideos 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 nils come out at the end, then I would want i to be less than nil.
  • But if I want to sort an array so that all the nils come out at the start, then I would want i to be greater than nil.

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 NSObjects 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



Leave a reply



Submit