Використання кортежів для порівняння кількох критеріїв
Дійсно простий спосіб проведення сортування за кількома критеріями (тобто сортування за одним порівнянням, а якщо еквівалентне, то за іншим порівнянням) полягає у використанні кортежів , оскільки оператори <
and >
мають для них перевантаження, які виконують лексикографічні порівняння.
public func < <A : Comparable, B : Comparable>(lhs: (A, B), rhs: (A, B)) -> Bool
Наприклад:
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)
Це lastName
спочатку порівняє властивості елементів . Якщо вони не рівні, порядок сортування базуватиметься на <
порівнянні з ними. Якщо вони є рівними, то він буде рухатися до наступної пари елементів в кортежі, тобто порівнюючиfirstName
властивості.
Стандартна бібліотека забезпечує <
і>
перевантажує кортежі від 2 до 6 елементів.
Якщо вам потрібні різні порядки сортування для різних властивостей, ви можете просто поміняти місцями елементи в кортежах:
contacts.sort {
($1.lastName, $0.firstName) <
($0.lastName, $1.firstName)
}
Тепер це буде сортуватися за lastName
спаданням, потім за firstName
зростанням.
Визначення sort(by:)
перевантаження, яке приймає кілька предикатів
Натхненний дискусією про сортування колекцій із map
закриттями та SortDescriptors , ще одним варіантом було б визначити власне перевантаження sort(by:)
та, sorted(by:)
що стосується декількох предикатів - де кожен предикат розглядається по черзі для вирішення порядку елементів.
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:
Параметр невдалий, але потрібний для того, щоб уникнути створення двозначностей при існуючому sort(by:)
перевантаженні)
Потім це дозволяє нам сказати (використовуючи contacts
масив з попередніх версій):
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 }
)
Незважаючи на те, що сайт виклику не такий стислий, як варіант кортежу, ви отримуєте додаткову ясність щодо того, що порівнюється та в якому порядку.
Відповідає Comparable
Якщо ви збираєтеся робити такого роду порівняння регулярно , то, як @AMomchilov і @appzYourLife запропонувати, ви можете відповідати 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)
}
}
А тепер просто зателефонуйте sort()
за зростанням:
contacts.sort()
або sort(by: >)
для спадання:
contacts.sort(by: >)
Визначення користувацьких порядків сортування у вкладеному типі
Якщо у вас є інші замовлення для сортування, які ви хочете використовувати, ви можете визначити їх у вкладеному типі:
extension Contact {
enum Comparison {
static let firstLastAscending: (Contact, Contact) -> Bool = {
return ($0.firstName, $0.lastName) <
($1.firstName, $1.lastName)
}
}
}
а потім просто зателефонувати як:
contacts.sort(by: Contact.Comparison.firstLastAscending)