Использование кортежей для сравнения нескольких критериев
Действительно простой способ выполнения сортировать по нескольким критериям (например , сортировка по одному сравнения, и если эквивалентны, то другим сравнения) является использование кортежей , как <
и >
операторы перегрузки для них , которые выполняют лексикографические сравнения.
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:)
перегрузки, которая принимает несколько предикатов
Вдохновленный обсуждение SORTING коллекций с 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)