Usar tuplas para hacer una comparación de varios criterios
Una forma realmente simple de realizar una ordenación por múltiples criterios (es decir, ordenando por una comparación, y si es equivalente, luego por otra comparación) es usando tuplas , ya que los operadores <
y >
tienen sobrecargas para ellos que realizan comparaciones lexicográficas.
public func < <A : Comparable, B : Comparable>(lhs: (A, B), rhs: (A, B)) -> Bool
Por ejemplo:
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)
Esto comparará las lastName
propiedades de los elementos primero. Si no son iguales, el orden de clasificación se basará en una <
comparación con ellos. Si son iguales, se moverá al siguiente par de elementos de la tupla, es decir, compararáfirstName
propiedades.
La biblioteca estándar proporciona <
y>
sobrecarga para tuplas de 2 a 6 elementos.
Si desea diferentes órdenes de clasificación para diferentes propiedades, simplemente puede intercambiar los elementos en las tuplas:
contacts.sort {
($1.lastName, $0.firstName) <
($0.lastName, $1.firstName)
}
Esto ahora se ordenará lastName
descendiendo y luego firstName
ascendiendo.
Definiendo un sort(by:)
sobrecarga que toma múltiples predicados
Inspirado por la discusión sobre la clasificación de colecciones con map
cierres y SortDescriptors , otra opción sería definir una sobrecarga personalizada de sort(by:)
y sorted(by:)
que trata con múltiples predicados, donde cada predicado se considera a su vez para decidir el orden de los elementos.
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
}
}
}
(El secondPredicate:
parámetro es desafortunado, pero es necesario para evitar crear ambigüedades con elsort(by:)
sobrecarga )
Esto entonces nos permite decir (usando la contacts
matriz de antes):
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 }
)
Aunque el sitio de llamadas no es tan conciso como la variante de tupla, obtiene una claridad adicional con lo que se está comparando y en qué orden.
De acuerdo a Comparable
Si va a hacer este tipo de comparaciones con regularidad, como sugieren @AMomchilov y @appzYourLife , puede cumplir Contact
con 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)
}
}
Y ahora solo pide sort()
un orden ascendente:
contacts.sort()
o sort(by: >)
por orden descendente:
contacts.sort(by: >)
Definición de órdenes de clasificación personalizadas en un tipo anidado
Si tiene otros órdenes de clasificación que desea utilizar, puede definirlos en un tipo anidado:
extension Contact {
enum Comparison {
static let firstLastAscending: (Contact, Contact) -> Bool = {
return ($0.firstName, $0.lastName) <
($1.firstName, $1.lastName)
}
}
}
y luego simplemente llame como:
contacts.sort(by: Contact.Comparison.firstLastAscending)