If I have an array of optional strings, and I want to sort it in ascending order with nils at the beginning, I can do it easily in a single line:
[\"b\", nil
You can provide a custom comparator which considers nil
as larger than any non-nil value:
let array = ["b", nil, "a", nil]
let sortedArray = array.sorted { (lhs, rhs) -> Bool in
switch (lhs, rhs) {
case let(l?, r?): return l < r // Both lhs and rhs are not nil
case (nil, _): return false // Lhs is nil
case (_?, nil): return true // Lhs is not nil, rhs is nil
}
}
print(sortedArray) // [Optional("a"), Optional("b"), nil, nil]
This works with any array of optional comparable elements, and avoids the usage of “magical large” values. The comparator can be implemented as a generic function:
func compareOptionalsWithLargeNil<T: Comparable>(lhs: T?, rhs: T?) -> Bool {
switch (lhs, rhs) {
case let(l?, r?): return l < r // Both lhs and rhs are not nil
case (nil, _): return false // Lhs is nil
case (_?, nil): return true // Lhs is not nil, rhs is nil
}
}
print(["b", nil, "a", nil].sorted(by: compareOptionalsWithLargeNil))
// [Optional("a"), Optional("b"), nil, nil]
print([2, nil, 1].sorted(by: compareOptionalsWithLargeNil))
// [Optional(1), Optional(2), nil]
print([3.0, nil, 1.0].sorted(by: compareOptionalsWithLargeNil))
// [Optional(1.0), Optional(3.0), nil]
print([Date(), nil, .distantPast, nil, .distantFuture].sorted(by: compareOptionalsWithLargeNil))
// [Optional(0000-12-30 00:00:00 +0000), Optional(2018-11-22 13:56:03 +0000),
// Optional(4001-01-01 00:00:00 +0000), nil, nil]
Your example with Int
gives a clue. If we had a max string value, we could plug that in.
This works for strings that contain only alphabetic characters:
let maxString = "~"
["b", nil, "a"].sorted{ $0 ?? maxString < $1 ?? maxString }
Or simply:
["b", nil, "a"].sorted{ $0 ?? "~" < $1 ?? "~" }
One nil
is indistinguishable from another. So if you have a working solution that happens to sort as you desire except that nil
entries wind up at the start, use it and then remove the nil
entries and append the same number of nil
entries to the end.
Example:
var arr : [String?] = [nil, "b", nil, "a", nil]
arr = arr.sorted{ $0 ?? "" < $1 ?? "" }
if let ix = arr.firstIndex(where: {$0 != nil}) {
arr = arr.suffix(from: ix) + Array(repeating: nil, count: ix)
}
// [Optional("a"), Optional("b"), nil, nil, nil]