Utilisation de tuples pour faire une comparaison de plusieurs critères
Un moyen très simple d'effectuer un tri selon plusieurs critères (c'est-à-dire le tri par une comparaison, et si équivalent, puis par une autre comparaison) est d'utiliser des tuples , car les opérateurs <
et >
ont des surcharges pour eux qui effectuent des comparaisons lexicographiques.
/// 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
Par exemple:
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")
// ]
Cela comparera d' lastName
abord les propriétés des éléments . S'ils ne sont pas égaux, l'ordre de tri sera basé sur une <
comparaison avec eux. S'ils sont égaux, alors il passera à la prochaine paire d'éléments dans le tuple, c'est-à-dire en comparant les firstName
propriétés.
La bibliothèque standard fournit <
et >
surcharge les tuples de 2 à 6 éléments.
Si vous voulez différents ordres de tri pour différentes propriétés, vous pouvez simplement permuter les éléments dans les tuples:
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"),
// ]
Cela va maintenant trier par lastName
décroissant, puis firstName
croissant.
Définir un sort(by:)
surcharge qui prend plusieurs prédicats
Inspiré par la discussion sur le tri des collections avec map
fermetures et SortDescriptors , une autre option serait de définir une surcharge personnalisée de sort(by:)
et sorted(by:)
qui traite de plusieurs prédicats - où chaque prédicat est considéré à son tour pour décider de l'ordre des éléments.
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
}
}
}
(Le secondPredicate:
paramètre est malheureux, mais est nécessaire pour éviter de créer des ambiguïtés avec la sort(by:)
surcharge existante )
Cela nous permet alors de dire (en utilisant le contacts
tableau de plus tôt):
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
// ...
)
Bien que le site d'appel ne soit pas aussi concis que la variante tuple, vous gagnez en clarté supplémentaire avec ce qui est comparé et dans quel ordre.
Se conformer à Comparable
Si vous comptez faire ce genre de comparaisons régulièrement, comme le suggèrent @AMomchilov & @appzYourLife , vous pouvez vous conformer 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)
}
}
Et maintenant, appelez simplement sort()
pour un ordre croissant:
contacts.sort()
ou sort(by: >)
pour un ordre décroissant:
contacts.sort(by: >)
Définition d'ordres de tri personnalisés dans un type imbriqué
Si vous souhaitez utiliser d'autres ordres de tri, vous pouvez les définir dans un type imbriqué:
extension Contact {
enum Comparison {
static let firstLastAscending: (Contact, Contact) -> Bool = {
return ($0.firstName, $0.lastName) <
($1.firstName, $1.lastName)
}
}
}
puis appelez simplement comme:
contacts.sort(by: Contact.Comparison.firstLastAscending)