Używanie krotek do porównywania wielu kryteriów
Naprawdę prostym sposobem sortowania według wielu kryteriów (tj. Sortowania według jednego porównania, a jeśli jest równoważne, to przez inne porównanie) jest użycie krotek , ponieważ operatory <i >mają dla nich przeciążenia, które wykonują porównania leksykograficzne.
public func < <A : Comparable, B : Comparable>(lhs: (A, B), rhs: (A, B)) -> Bool
Na przykład:
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)
Spowoduje to najpierw porównanie lastNamewłaściwości elementów . Jeśli nie są równe, kolejność sortowania będzie oparta na <porównaniu z nimi. Jeśli są równe, to przejdzie do następnej pary elementów w krotce, czyli porównując firstNamewłaściwości.
Biblioteka standardowa zapewnia <i >przeciąża krotki od 2 do 6 elementów.
Jeśli chcesz mieć różne porządki sortowania dla różnych właściwości, możesz po prostu zamienić elementy w krotkach:
contacts.sort {
($1.lastName, $0.firstName) <
($0.lastName, $1.firstName)
}
To będzie teraz sortowane lastNamemalejąco, a następnie firstNamerosnąco.
Definiowanie sort(by:)przeciążenia, które przyjmuje wiele predykatów
Zainspirowana dyskusją na temat sortowania kolekcji z mapdomknięciami i SortDescriptors , inną opcją byłoby zdefiniowanie niestandardowego przeciążenia sort(by:)i sorted(by:)zajmującego się wieloma predykatami - gdzie każdy predykat jest z kolei brany pod uwagę przy decydowaniu o kolejności elementów.
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:Parametr jest niefortunny, ale jest wymagany, aby uniknąć tworzenia niejednoznaczności z istniejącym sort(by:)przeciążeniem)
To pozwala nam powiedzieć (używając contactswcześniejszej tablicy):
contacts.sort(by:
{ $0.lastName > $1.lastName },
{ $0.firstName < $1.firstName }
)
print(contacts)
let sortedContacts = contacts.sorted(by:
{ $0.lastName > $1.lastName },
{ $0.firstName < $1.firstName }
)
Chociaż witryna wywoławcza nie jest tak zwięzła jak wariant krotki, zyskujesz dodatkową jasność co do tego, co jest porównywane i w jakiej kolejności.
Zgodne z Comparable
Jeśli zamierzasz regularnie przeprowadzać tego rodzaju porównania, to, jak sugerują @AMomchilov i @appzYourLife , możesz dostosować się Contactdo 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)
}
}
A teraz po prostu poproś sort()o kolejność rosnącą:
contacts.sort()
lub sort(by: >)malejąco:
contacts.sort(by: >)
Definiowanie niestandardowych porządków sortowania w typie zagnieżdżonym
Jeśli masz inne porządki sortowania, których chcesz użyć, możesz zdefiniować je w typie zagnieżdżonym:
extension Contact {
enum Comparison {
static let firstLastAscending: (Contact, Contact) -> Bool = {
return ($0.firstName, $0.lastName) <
($1.firstName, $1.lastName)
}
}
}
a następnie zadzwoń jako:
contacts.sort(by: Contact.Comparison.firstLastAscending)