0

I have this struct:

struct HostModel {
    var hostName: String?
    var hostPhoneNumber: String?
    var hostEmail: String?  
}    

var hosts = [HostModel]()

This is populated by downloading from Firebase Database.

//Getting the data....     


  let hostData = HostModel(hostName: dict["hostName"] as? String, hostPhoneNumber: dict["hostPhoneNumber"] as? String, hostEmail: dict["hostEmail"] as? String)
                        self.hosts.append(hostData)    

All is fine until I'm trying to sort the struct:

self.hosts.sorted { (lhs, rhs) -> Bool in
                    return lhs.hostName < rhs.hostName
                }    

This gives the error:

Binary operator '<' cannot be applied to two 'String?' operands

Catch
  • 153
  • 1
  • 14
  • 4
    Read the error message. `lhs` and `rhs` are *optional* strings. How should they be compared? Is `nil` smaller or larger than "foo"? – Martin R Jan 15 '20 at 12:22
  • 1
    Following on @MartinR comment, just unwrap the strings (preferably with a safe unwrap, but that's up to you), and you'll be able to compare them. – IloneSP Jan 15 '20 at 12:33
  • filter nils before sorting. `hosts.compactMap { $0.hostName }.sorted()` – Blazej SLEBODA Jan 15 '20 at 12:44

1 Answers1

0

You cannot compare optional strings, as written in the error message. Unwrap the strings and then try to compare.

self.hosts.sorted { lhs, rhs in
    guard let lhsName = lhs.hostName, let rhsName = rhs.hostName else { return false }
    return lhsName < rhsName
}

EDIT - The above solution is incorrect as it breaks the transitivity of the sort() function and will not work with large sets of data; Thanks to @Alexander - Reinstate Monica for pointing out.

A proper solution would be to either force-unwrap the values which is not recommended or provide a nil-coalescing value like so:

let sortedArray = self.hosts.sorted { lhs, rhs in
    lhs.hostName ?? "" < rhs.hostName ?? ""
}
Eilon
  • 2,698
  • 3
  • 16
  • 32
  • 1
    I'm pretty sure this breaks the transitivity of sorting. `(nil, str)` is < than `(str, nil)`, which is < than `(nil, str)`. That's invalid. – Alexander Jan 15 '20 at 12:37
  • @Alexander-ReinstateMonica I'm not quite sure what you mean here. The above code works properly, as we first unwrap the values of the `host` object we want to sort by. Returning `false` on the `guard` check will position the `host` with the nil value at the back of the array, returning `true` will position it in the front. There may be cleaner ways to perform such sorting but I've tried to base my answer as close to OP's implementation as possible. – Eilon Jan 15 '20 at 13:02
  • 1
    @Alexander is correct: The comparator must be a “strict weak ordering,” compare https://developer.apple.com/documentation/swift/array/2296801-sort. In particular it must be transitive. This might work by chance, but that is not guaranteed. – Martin R Jan 15 '20 at 13:08
  • @Eilon You've broken the transitive property of the sorting closure, which is necessary for it to work correctly. You've encoded something similar to `1 < 2 < 1`. Sorting algorithms encode optimizations like: if `x < y` and `y < z`, then I know `x < z` without checking them separately. Yet in your case, you're saying `a < b`, `b < c`, but also (erroneously) `c < a`. Sort a big enough data set like this, and you'll see that it fails entirely. – Alexander Jan 15 '20 at 13:20
  • @Eilon Here's an example: https://repl.it/@alexandermomchilov/Breaking-transitivity-of-sorting it's totally broken. – Alexander Jan 15 '20 at 13:31
  • @Alexander-ReinstateMonica I understand, thanks for letting me know. I will edit the answer. – Eilon Jan 15 '20 at 13:59
  • @Eilon `lhs.hostName ?? "" < rhs.hostName ?? ""` isn't a good approach, either. I think it encodes the idea that "nil comes first" (because empty strings compare smaller than any other strings, IIRC), but it does so in an implicit, non-intuitive way. Look at the answer: https://stackoverflow.com/a/44808567/3141234 I know I'm biased, but I think that's a much better approach. For one, it generalizes to all types (not just those that have a convenient way to initializing an "empty" instance like `false`/`0`/`""`, but also makes it explicit where the `nil`s would go. – Alexander Jan 15 '20 at 14:55