Verwenden von Tupeln zum Vergleichen mehrerer Kriterien
Eine wirklich einfache Möglichkeit, eine Sortierung nach mehreren Kriterien durchzuführen (dh nach einem Vergleich zu sortieren und, falls gleichwertig, nach einem anderen Vergleich), ist die Verwendung von Tupeln , da die Operatoren <
und >
für sie Überladungen haben, die lexikografische Vergleiche durchführen.
/// 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
Beispielsweise:
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")
// ]
Dadurch werden zuerst die lastName
Eigenschaften der Elemente verglichen . Wenn sie nicht gleich sind, basiert die Sortierreihenfolge auf einem <
Vergleich mit ihnen. Wenn sie sind gleich, dann wird es auf das nächste Paar von Elementen in dem Tupel zu bewegen, dh die Vergleichs firstName
Eigenschaften.
Die Standardbibliothek bietet <
und >
überlädt Tupel mit 2 bis 6 Elementen.
Wenn Sie unterschiedliche Sortierreihenfolgen für unterschiedliche Eigenschaften wünschen, können Sie einfach die Elemente in den Tupeln austauschen:
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"),
// ]
Dies wird nun nach lastName
absteigend und dann firstName
aufsteigend sortiert .
Definieren einer sort(by:)
Überladung, die mehrere Prädikate benötigt
Inspiriert von der Diskussion über das Sortieren von Sammlungen mit map
Closures und SortDescriptors besteht eine weitere Option darin, eine benutzerdefinierte Überladung von sort(by:)
und sorted(by:)
mehrere Prädikate zu definieren, wobei jedes Prädikat der Reihe nach berücksichtigt wird, um die Reihenfolge der Elemente zu bestimmen.
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
}
}
}
(Der secondPredicate:
Parameter ist unglücklich, wird jedoch benötigt, um Mehrdeutigkeiten mit der vorhandenen sort(by:)
Überlastung zu vermeiden. )
Dies erlaubt uns dann zu sagen (unter Verwendung des contacts
Arrays von früher):
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
// ...
)
Obwohl die Anrufstelle nicht so präzise ist wie die Tupelvariante, erhalten Sie zusätzliche Klarheit darüber, was in welcher Reihenfolge verglichen wird.
Entsprechend Comparable
Wenn Sie vorhaben , regelmäßig dann zu tun , diese Art von Vergleichen werden, wie @AMomchilov & @appzYourLife vorschlagen, können Sie anpassen Contact
zu 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)
}
}
Und jetzt fordern Sie einfach sort()
eine aufsteigende Reihenfolge:
contacts.sort()
oder sort(by: >)
für eine absteigende Reihenfolge:
contacts.sort(by: >)
Benutzerdefinierte Sortierreihenfolgen in einem verschachtelten Typ definieren
Wenn Sie andere Sortierreihenfolgen verwenden möchten, können Sie diese in einem verschachtelten Typ definieren:
extension Contact {
enum Comparison {
static let firstLastAscending: (Contact, Contact) -> Bool = {
return ($0.firstName, $0.lastName) <
($1.firstName, $1.lastName)
}
}
}
und dann einfach anrufen als:
contacts.sort(by: Contact.Comparison.firstLastAscending)