Reputation: 219
I am attempting to sort an array which includes variables which are nil.
class TestArray {
var a: String? = nil
var b: String? = nil
init(a: String?, b: String?) {
self.a = a
self.b = b
}
}
let first = TestArray(a: "xxx", b: "yyy")
let second = TestArray(a: "zzz", b: "zzz")
let third = TestArray(a: "aaa", b: nil)
var array = [first, second, third]
array.sort(by: {($0.a as String!) > ($1.a as String!)})
array.sort(by: {($0.b as String!) > ($1.b as String!)}) // Throws an error
How can I sort by b
and leave the third TestArray with b = nil at the end of the array?
Also I would like to sort through all b and then for the arrays in which b = nil sort the remaining by a.
Upvotes: 0
Views: 530
Reputation: 63359
Here's (in my opinion) the best way:
First, I define a <
operator for String?
instances.
It operates for the specification that nil
compares as less than any other string, so it'll appear first in sorting order.
fileprivate func <(a: String?, b: String?) -> Bool {
switch (a, b) {
case (nil, nil): return false
case (nil, _?): return true
case (_?, nil): return false
case (let a?, let b?): return a < b
}
}
Next, I wrote a sorting predicate that uses that operator. It works for the specification that instances are to be sorted first by their a
. If those are equal, ties are broken by sorting by their b
.
struct TestStruct {
var a: String?
var b: String?
}
let input = [
TestStruct(a: "xxx", b: "yyy"),
TestStruct(a: "zzz", b: "zzz"),
TestStruct(a: "aaa", b: nil)
]
let output = input.sorted { lhs, rhs in
if lhs.a != rhs.a { return lhs.a < rhs.a } // first sort by a
if lhs.b != rhs.b { return lhs.b < rhs.b } // then sort by b
return true
}
print(output)
//[
// TempCode.TestStruct(a: Optional("aaa"), b: nil),
// TempCode.TestStruct(a: Optional("xxx"), b: Optional("yyy")),
// TempCode.TestStruct(a: Optional("zzz"), b: Optional("zzz"))
//]
*I made TestStruct
a struct just so I can use the synthesized initializer and automatic print behaviour. It'll work just the same if it's a class.
If you'll be comparing TestStruct
a lot, and it makes sense for there to be a standard order to comparing it, then it's best you add conformance to Comparable
. Doing so also requires conformance to Equatable
:
extension TestStruct: Equatable {
public static func ==(lhs: TestStruct, rhs: TestStruct) -> Bool {
return
lhs.a == rhs.a &&
lhs.b == rhs.b
}
}
extension TestStruct: Comparable {
public static func <(lhs: TestStruct, rhs: TestStruct) -> Bool {
if lhs.a != rhs.a { return lhs.a < rhs.a } // first sort by a
if lhs.b != rhs.b { return lhs.b < rhs.b } // then sort by b
return true
}
}
Now you can sort your array easily with the sorting behaviour specified by TestStruct
:
let output = input.sorted()
You can see it in action here.
Upvotes: 2
Reputation: 7756
I believe this should cover the situations you want without having to iterate more than once (which is expensive)
array.sort { (lessThan, item) -> Bool in
switch (lessThan.a, lessThan.b, item.a, item.b) {
// if bs exist, use that comparison
case (_, .some(let lessThanB), _, .some(let itemB)):
return lessThanB < itemB
// if bs don't exist by as do, use that comparison
case (.some(let lessThanA), .none, .some(let itemA), .none):
return lessThanA < itemA
// if one item has a value but the other doesn't, the item with the value should be first
case (_, .some(_), _, .none), (.some(_), _, .none, _ ):
return true
default:
return false
}
}
Upvotes: 3
Reputation: 73206
In case you needn't necessarily sort in place, you could simply apply the "algorithm" you describe in a brute-force fashion to sort array
using several filter
followed sorted
for the subset arrays.
"Algorithm":
b
property, given that it is not nil
.b
property is nil
, sort by a
property.a
and b
are nil
at the end of the sorted array.E.g.:
// lets set up a more interesting example
let first = TestArray(a: "xxx", b: "yyy")
let second = TestArray(a: "ccc", b: nil)
let third = TestArray(a: "aaa", b: nil)
let fourth = TestArray(a: "zzz", b: "zzz")
let fifth = TestArray(a: "bbb", b: nil)
var array = [first, second, third, fourth, fifth]
let sorted = array.filter { $0.b != nil }.sorted { $0.b! < $1.b! } +
array.filter { $0.b == nil && $0.a != nil }.sorted { $0.a! < $1.a! } +
array.filter { $0.b == nil && $0.a == nil }
sorted.enumerated().forEach { print("\($0): b: \($1.b), a: \($1.a)") }
/* 0: b: Optional("yyy"), a: Optional("xxx")
1: b: Optional("zzz"), a: Optional("zzz")
2: b: nil, a: Optional("aaa")
3: b: nil, a: Optional("bbb")
4: b: nil, a: Optional("ccc") */
Upvotes: 1