使用元组对多个条件进行比较
通过多个条件执行排序的一种非常简单的方法(即,通过一个比较进行排序,如果相等,则通过另一个比较进行排序)是使用元组,因为<
and >
运算符对其执行字典式比较的操作有重载。
/// Returns a Boolean value indicating whether the first tuple is ordered
/// before the second in a lexicographical ordering.
///
/// Given two tuples `(a1, a2, ..., aN)` and `(b1, b2, ..., bN)`, the first
/// tuple is before the second tuple if and only if
/// `a1 < b1` or (`a1 == b1` and
/// `(a2, ..., aN) < (b2, ..., bN)`).
public func < <A : Comparable, B : Comparable>(lhs: (A, B), rhs: (A, B)) -> Bool
例如:
struct Contact {
var firstName: String
var lastName: String
}
var contacts = [
Contact(firstName: "Leonard", lastName: "Charleson"),
Contact(firstName: "Michael", lastName: "Webb"),
Contact(firstName: "Charles", lastName: "Alexson"),
Contact(firstName: "Michael", lastName: "Elexson"),
Contact(firstName: "Alex", lastName: "Elexson"),
]
contacts.sort {
($0.lastName, $0.firstName) <
($1.lastName, $1.firstName)
}
print(contacts)
// [
// Contact(firstName: "Charles", lastName: "Alexson"),
// Contact(firstName: "Leonard", lastName: "Charleson"),
// Contact(firstName: "Alex", lastName: "Elexson"),
// Contact(firstName: "Michael", lastName: "Elexson"),
// Contact(firstName: "Michael", lastName: "Webb")
// ]
这将首先比较元素的lastName
属性。如果它们不相等,则排序顺序将基于<
与它们的比较。如果它们是相等的,那么它会移动到在元组中的下一对元素,比较即该firstName
性质。
标准库为2到6个元素的元组提供<
并>
重载。
如果要为不同的属性使用不同的排序顺序,则可以简单地交换元组中的元素:
contacts.sort {
($1.lastName, $0.firstName) <
($0.lastName, $1.firstName)
}
// [
// Contact(firstName: "Michael", lastName: "Webb")
// Contact(firstName: "Alex", lastName: "Elexson"),
// Contact(firstName: "Michael", lastName: "Elexson"),
// Contact(firstName: "Leonard", lastName: "Charleson"),
// Contact(firstName: "Charles", lastName: "Alexson"),
// ]
现在lastName
,将按降序排序,然后firstName
升序排序。
定义sort(by:)
需要多个谓词的重载
受有关使用map
闭包和SortDescriptor 对集合进行排序的讨论的启发,另一个选择是定义的自定义重载,sort(by:)
并sorted(by:)
处理多个谓词-在此依次考虑每个谓词来确定元素的顺序。
extension MutableCollection where Self : RandomAccessCollection {
mutating func sort(
by firstPredicate: (Element, Element) -> Bool,
_ secondPredicate: (Element, Element) -> Bool,
_ otherPredicates: ((Element, Element) -> Bool)...
) {
sort(by:) { lhs, rhs in
if firstPredicate(lhs, rhs) { return true }
if firstPredicate(rhs, lhs) { return false }
if secondPredicate(lhs, rhs) { return true }
if secondPredicate(rhs, lhs) { return false }
for predicate in otherPredicates {
if predicate(lhs, rhs) { return true }
if predicate(rhs, lhs) { return false }
}
return false
}
}
}
extension Sequence {
mutating func sorted(
by firstPredicate: (Element, Element) -> Bool,
_ secondPredicate: (Element, Element) -> Bool,
_ otherPredicates: ((Element, Element) -> Bool)...
) -> [Element] {
return sorted(by:) { lhs, rhs in
if firstPredicate(lhs, rhs) { return true }
if firstPredicate(rhs, lhs) { return false }
if secondPredicate(lhs, rhs) { return true }
if secondPredicate(rhs, lhs) { return false }
for predicate in otherPredicates {
if predicate(lhs, rhs) { return true }
if predicate(rhs, lhs) { return false }
}
return false
}
}
}
(该secondPredicate:
参数是不幸的,但是它是必需的,以避免与现有的sort(by:)
重载产生歧义)
然后,这使我们可以说(使用contacts
前面的数组):
contacts.sort(by:
{ $0.lastName > $1.lastName }, // first sort by lastName descending
{ $0.firstName < $1.firstName } // ... then firstName ascending
// ...
)
print(contacts)
// [
// Contact(firstName: "Michael", lastName: "Webb")
// Contact(firstName: "Alex", lastName: "Elexson"),
// Contact(firstName: "Michael", lastName: "Elexson"),
// Contact(firstName: "Leonard", lastName: "Charleson"),
// Contact(firstName: "Charles", lastName: "Alexson"),
// ]
// or with sorted(by:)...
let sortedContacts = contacts.sorted(by:
{ $0.lastName > $1.lastName }, // first sort by lastName descending
{ $0.firstName < $1.firstName } // ... then firstName ascending
// ...
)
尽管呼叫站点不像元组变体那么简洁,但是您可以通过比较什么和以什么顺序获得更多的清晰度。
符合 Comparable
如果你打算做这些类型的比较有规律的话,作为@AMomchilov&@appzYourLife建议,你可以遵循Contact
到Comparable
:
extension Contact : Comparable {
static func == (lhs: Contact, rhs: Contact) -> Bool {
return (lhs.firstName, lhs.lastName) ==
(rhs.firstName, rhs.lastName)
}
static func < (lhs: Contact, rhs: Contact) -> Bool {
return (lhs.lastName, lhs.firstName) <
(rhs.lastName, rhs.firstName)
}
}
现在,只需调用sort()
升序:
contacts.sort()
或sort(by: >)
降序排列:
contacts.sort(by: >)
定义嵌套类型的自定义排序顺序
如果您还有其他要使用的排序顺序,则可以以嵌套类型定义它们:
extension Contact {
enum Comparison {
static let firstLastAscending: (Contact, Contact) -> Bool = {
return ($0.firstName, $0.lastName) <
($1.firstName, $1.lastName)
}
}
}
然后只需调用为:
contacts.sort(by: Contact.Comparison.firstLastAscending)