Utilizzo delle tuple per confrontare più criteri
Un modo molto semplice per eseguire un ordinamento in base a criteri multipli (cioè ordinare in base a un confronto e, se equivalente, a un altro confronto) è usare le tuple , poiché gli operatori <
e >
hanno sovraccarichi per loro che eseguono confronti lessicografici.
/// 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
Per esempio:
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")
// ]
Questo confronterà lastName
prima le proprietà degli elementi . Se non sono uguali, l'ordinamento sarà basato su un <
confronto con loro. Se sono uguali, si sposterà sulla successiva coppia di elementi nella tupla, ovvero confrontando le firstName
proprietà.
La libreria standard fornisce <
e >
sovraccarica le tuple da 2 a 6 elementi.
Se desideri diversi ordini di ordinamento per proprietà diverse, puoi semplicemente scambiare gli elementi nelle tuple:
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"),
// ]
Questo verrà ora ordinato in ordine lastName
discendente, quindi firstName
ascendente.
Definizione di un sort(by:)
sovraccarico che accetta più predicati
Ispirato dalla discussione sull'ordinamento delle raccolte con map
chiusure e SortDescriptors , un'altra opzione potrebbe essere quella di definire un sovraccarico personalizzato di sort(by:)
e sorted(by:)
che si occupa di più predicati, in cui ogni predicato viene considerato a sua volta per decidere l'ordine degli elementi.
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
}
}
}
(Il secondPredicate:
parametro è sfortunato, ma è necessario per evitare di creare ambiguità con l' sort(by:)
overload esistente )
Questo poi ci permette di dire (usando l' contacts
array di prima):
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
// ...
)
Sebbene il sito di chiamata non sia conciso come la variante della tupla, ottieni ulteriore chiarezza su cosa viene confrontato e in quale ordine.
Conforme a Comparable
Se farai regolarmente questo tipo di confronti, come suggeriscono @AMomchilov e @appzYourLife , puoi conformarti Contact
a 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)
}
}
E ora chiedi solo sort()
un ordine crescente:
contacts.sort()
o sort(by: >)
per un ordine decrescente:
contacts.sort(by: >)
Definizione di ordinamenti personalizzati in un tipo nidificato
Se disponi di altri ordinamenti che desideri utilizzare, puoi definirli in un tipo nidificato:
extension Contact {
enum Comparison {
static let firstLastAscending: (Contact, Contact) -> Bool = {
return ($0.firstName, $0.lastName) <
($1.firstName, $1.lastName)
}
}
}
e quindi chiama semplicemente come:
contacts.sort(by: Contact.Comparison.firstLastAscending)