У меня есть массив Contact
объектов:
var contacts:[Contact] = [Contact]()
Контактный класс:
Class Contact:NSOBject {
var firstName:String!
var lastName:String!
}
И я хотел бы отсортировать этот массив lastName
постепенно, firstName
если у некоторых контактов будет то же самое lastName
.
Я могу сортировать по одному из этих критериев, но не по обоим.
contacts.sortInPlace({$0.lastName < $1.lastName})
Как я могу добавить дополнительные критерии для сортировки этого массива?
Contact
вероятно, не должно наследоватьNSObject
, 2)Contact
вероятно, должно быть структурой и 3)firstName
и,lastName
вероятно, не должно быть неявно развернутыми опциями.Ответы:
Подумайте, что означает «сортировка по нескольким критериям». Это означает, что сначала сравниваются два объекта по одному критерию. Затем, если эти критерии совпадают, связи будут разорваны по следующим критериям, и так далее, пока вы не получите желаемый порядок.
let sortedContacts = contacts.sort { if $0.lastName != $1.lastName { // first, compare by last names return $0.lastName < $1.lastName } /* last names are the same, break ties by foo else if $0.foo != $1.foo { return $0.foo < $1.foo } ... repeat for all other fields in the sorting */ else { // All other fields are tied, break ties by last name return $0.firstName < $1.firstName } }
Здесь вы видите
Sequence.sorted(by:)
метод , который обращается к предоставленному закрытию, чтобы определить, как сравниваются элементы.Если ваша сортировка будет использоваться во многих местах, возможно, лучше будет привести ваш тип в соответствие с
Comparable
протоколом . Таким образом, вы можете использоватьSequence.sorted()
метод , который обращается к вашей реализацииComparable.<(_:_:)
оператора, чтобы определить, как сравниваются элементы. Таким образом, вы можете сортировать любойSequence
изContact
s без необходимости дублировать код сортировки.источник
else
Тело должно быть в пределах ,{ ... }
иначе код не компилируется.sort
vs.sortInPlace
см. здесь . АОЛО увидеть это ниже, это гораздо более модульнымsortInPlace
больше НЕ доступен в Swift 3, вместо него вы должны использоватьsort()
.sort()
изменит сам массив. Также есть новая функция с именем,sorted()
которая будет возвращать отсортированный массив==
- не лучшая идея. Работает только для 2 объектов. Более того, и вы начинаете повторяться с множеством составных логических выраженийИспользование кортежей для сравнения нескольких критериев
Действительно простой способ выполнения сортировать по нескольким критериям (например , сортировка по одному сравнения, и если эквивалентны, то другим сравнения) является использование кортежей , как
<
и>
операторы перегрузки для них , которые выполняют лексикографические сравнения./// 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
Например:
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") // ]
lastName
Сначала будут сравниваться свойства элементов . Если они не равны, то порядок сортировки будет основан на<
сравнении с ними. Если они являются равными, то он будет двигаться к следующей паре элементов в кортеже, то есть сравниваяfirstName
свойства.Стандартная библиотека предоставляет
<
и>
перегружает кортежи от 2 до 6 элементов.Если вам нужны разные порядки сортировки для разных свойств, вы можете просто поменять местами элементы в кортежах:
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"), // ]
Теперь он будет отсортирован по
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 }, // 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 // ... )
Хотя сайт вызова не такой лаконичный, как вариант с кортежем, вы получаете дополнительную ясность в отношении того, что сравнивается и в каком порядке.
В соответствии с
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)
источник
contacts.sort { ($0.lastName, $0.firstName) < ($1.lastName, $1.firstName) }
Помогло. Спасибоcontacts.sort { ($0.lastName ?? "", $0.firstName ?? "") < ($1.lastName ?? "", $1.firstName ?? "") }
.""
сравнения с другими строками (это предшествует непустым строкам). Это своего рода неявное, своего рода магия и негибкость, если вы хотите,nil
чтобы вместо этого s располагались в конце списка. Я рекомендую вам взглянуть на моюnilComparator
функцию stackoverflow.com/a/44808567/3141234Еще один простой подход для сортировки по 2 критериям показан ниже.
Проверьте первое поле, в данном случае оно есть
lastName
, если они не равны, сортировать поlastName
, еслиlastName
равны, то в данном случае сортировать по второму полюfirstName
.contacts.sort { $0.lastName == $1.lastName ? $0.firstName < $1.firstName : $0.lastName < $1.lastName }
источник
Единственное, что лексикографическая сортировка не может сделать, как описано в @Hamish, - это обрабатывать разные направления сортировки, скажем, сортировку по первому полю по убыванию, по следующему полю по возрастанию и т. Д.
Я создал сообщение в блоге о том, как это сделать в Swift 3, и сохраняю код простым и читаемым.
Вы можете найти это здесь:
http://master-method.com/index.php/2016/11/23/sort-a-sequence-ie-arrays-of-objects-by-multiple-properties-in-swift-3/Вы также можете найти репозиторий GitHub с кодом здесь:
https://github.com/jallauca/SortByMultipleFieldsSwift.playground
Суть всего этого, скажем, если у вас есть список мест, вы сможете это сделать:
struct Location { var city: String var county: String var state: String } var locations: [Location] { return [ Location(city: "Dania Beach", county: "Broward", state: "Florida"), Location(city: "Fort Lauderdale", county: "Broward", state: "Florida"), Location(city: "Hallandale Beach", county: "Broward", state: "Florida"), Location(city: "Delray Beach", county: "Palm Beach", state: "Florida"), Location(city: "West Palm Beach", county: "Palm Beach", state: "Florida"), Location(city: "Savannah", county: "Chatham", state: "Georgia"), Location(city: "Richmond Hill", county: "Bryan", state: "Georgia"), Location(city: "St. Marys", county: "Camden", state: "Georgia"), Location(city: "Kingsland", county: "Camden", state: "Georgia"), ] } let sortedLocations = locations .sorted(by: ComparisonResult.flip <<< Location.stateCompare, Location.countyCompare, Location.cityCompare )
источник
На этот вопрос уже есть много отличных ответов, но я хочу указать на статью - Дескрипторы сортировки в Swift . У нас есть несколько способов сортировки по множеству критериев.
При использовании NSSortDescriptor у этого способа есть некоторые ограничения, объект должен быть классом и унаследован от NSObject.
class Person: NSObject { var first: String var last: String var yearOfBirth: Int init(first: String, last: String, yearOfBirth: Int) { self.first = first self.last = last self.yearOfBirth = yearOfBirth } override var description: String { get { return "\(self.last) \(self.first) (\(self.yearOfBirth))" } } } let people = [ Person(first: "Jo", last: "Smith", yearOfBirth: 1970), Person(first: "Joe", last: "Smith", yearOfBirth: 1970), Person(first: "Joe", last: "Smyth", yearOfBirth: 1970), Person(first: "Joanne", last: "smith", yearOfBirth: 1985), Person(first: "Joanne", last: "smith", yearOfBirth: 1970), Person(first: "Robert", last: "Jones", yearOfBirth: 1970), ]
Здесь, например, мы хотим отсортировать по фамилии, затем по имени и, наконец, по году рождения. И мы хотим сделать это без учета регистра и с использованием локали пользователя.
let lastDescriptor = NSSortDescriptor(key: "last", ascending: true, selector: #selector(NSString.localizedCaseInsensitiveCompare(_:))) let firstDescriptor = NSSortDescriptor(key: "first", ascending: true, selector: #selector(NSString.localizedCaseInsensitiveCompare(_:))) let yearDescriptor = NSSortDescriptor(key: "yearOfBirth", ascending: true) (people as NSArray).sortedArray(using: [lastDescriptor, firstDescriptor, yearDescriptor]) // [Robert Jones (1970), Jo Smith (1970), Joanne smith (1970), Joanne smith (1985), Joe Smith (1970), Joe Smyth (1970)]
Использование быстрого способа сортировки по фамилии / имени. Этот способ должен работать как с классом / структурой. Однако здесь мы не сортируем по году рождения.
let sortedPeople = people.sorted { p0, p1 in let left = [p0.last, p0.first] let right = [p1.last, p1.first] return left.lexicographicallyPrecedes(right) { $0.localizedCaseInsensitiveCompare($1) == .orderedAscending } } sortedPeople // [Robert Jones (1970), Jo Smith (1970), Joanne smith (1985), Joanne smith (1970), Joe Smith (1970), Joe Smyth (1970)]
Быстрый способ установки NSSortDescriptor. Здесь используется концепция, согласно которой «функции - это первоклассный тип». SortDescriptor - это тип функции, принимает два значения, возвращает логическое значение. Скажем, sortByFirstName, мы берем два параметра ($ 0, $ 1) и сравниваем их имена. Функции объединения принимают кучу дескрипторов SortDescriptors, сравнивают их все и отдают приказы.
typealias SortDescriptor<Value> = (Value, Value) -> Bool let sortByFirstName: SortDescriptor<Person> = { $0.first.localizedCaseInsensitiveCompare($1.first) == .orderedAscending } let sortByYear: SortDescriptor<Person> = { $0.yearOfBirth < $1.yearOfBirth } let sortByLastName: SortDescriptor<Person> = { $0.last.localizedCaseInsensitiveCompare($1.last) == .orderedAscending } func combine<Value> (sortDescriptors: [SortDescriptor<Value>]) -> SortDescriptor<Value> { return { lhs, rhs in for isOrderedBefore in sortDescriptors { if isOrderedBefore(lhs,rhs) { return true } if isOrderedBefore(rhs,lhs) { return false } } return false } } let combined: SortDescriptor<Person> = combine( sortDescriptors: [sortByLastName,sortByFirstName,sortByYear] ) people.sorted(by: combined) // [Robert Jones (1970), Jo Smith (1970), Joanne smith (1970), Joanne smith (1985), Joe Smith (1970), Joe Smyth (1970)]
Это хорошо, потому что вы можете использовать его как со структурой, так и с классом, вы даже можете расширить его для сравнения с nils.
Тем не менее, настоятельно рекомендуется прочитать оригинальную статью . Там гораздо больше деталей и хорошо объяснено.
источник
Я бы рекомендовал использовать решение для кортежей Хэмиша, поскольку оно не требует дополнительного кода.
Если вам нужно что-то, что ведет себя как
if
операторы, но упрощает логику ветвления, вы можете использовать это решение, которое позволяет вам делать следующее:animals.sort { return comparisons( compare($0.family, $1.family, ascending: false), compare($0.name, $1.name)) }
Вот функции, которые позволяют вам это делать:
func compare<C: Comparable>(_ value1Closure: @autoclosure @escaping () -> C, _ value2Closure: @autoclosure @escaping () -> C, ascending: Bool = true) -> () -> ComparisonResult { return { let value1 = value1Closure() let value2 = value2Closure() if value1 == value2 { return .orderedSame } else if ascending { return value1 < value2 ? .orderedAscending : .orderedDescending } else { return value1 > value2 ? .orderedAscending : .orderedDescending } } } func comparisons(_ comparisons: (() -> ComparisonResult)...) -> Bool { for comparison in comparisons { switch comparison() { case .orderedSame: continue // go on to the next property case .orderedAscending: return true case .orderedDescending: return false } } return false // all of them were equal }
Если вы хотите проверить это, вы можете использовать этот дополнительный код:
enum Family: Int, Comparable { case bird case cat case dog var short: String { switch self { case .bird: return "B" case .cat: return "C" case .dog: return "D" } } public static func <(lhs: Family, rhs: Family) -> Bool { return lhs.rawValue < rhs.rawValue } } struct Animal: CustomDebugStringConvertible { let name: String let family: Family public var debugDescription: String { return "\(name) (\(family.short))" } } let animals = [ Animal(name: "Leopard", family: .cat), Animal(name: "Wolf", family: .dog), Animal(name: "Tiger", family: .cat), Animal(name: "Eagle", family: .bird), Animal(name: "Cheetah", family: .cat), Animal(name: "Hawk", family: .bird), Animal(name: "Puma", family: .cat), Animal(name: "Dalmatian", family: .dog), Animal(name: "Lion", family: .cat), ]
Основное отличие от решения Джейми заключается в том, что доступ к свойствам определяется встроенным, а не как статические методы / методы экземпляра в классе. Например,
$0.family
вместоAnimal.familyCompare
. И возрастание / убывание контролируется параметром, а не перегруженным оператором. Решение Джейми добавляет расширение к массиву, тогда как мое решение использует встроенный методsort
/,sorted
но требует определения двух дополнительных:compare
иcomparisons
.Для полноты картины вот как мое решение сравнивается с решением кортежа Хэмиша . Чтобы продемонстрировать, я буду использовать дикий пример, в котором мы хотим отсортировать людей по
(name, address, profileViews)
решению Хэмиша, которое оценит каждое из 6 значений свойств ровно один раз перед началом сравнения. Это может быть нежелательно или нежелательно. Например, предполагая, чтоprofileViews
сетевой вызов является дорогостоящим, мы можем избежатьprofileViews
его, если это не является абсолютно необходимым. Мое решение позволит избежать оценкиprofileViews
до$0.name == $1.name
и$0.address == $1.address
. Однако, когда он действительно оценивает,profileViews
он, вероятно, будет оценивать гораздо больше раз, чем один раз.источник
Как насчет:
contacts.sort() { [$0.last, $0.first].lexicographicalCompare([$1.last, $1.first]) }
источник
lexicographicallyPrecedes
требует, чтобы все типы в массиве были одинаковыми. Например[String, String]
. OP, вероятно, хочет смешивать и сопоставлять типы:[String, Int, Bool]
чтобы они могли это сделать[$0.first, $0.age, $0.isActive]
.это сработало для моего массива [String] в Swift 3, и кажется, что в Swift 4 все в порядке
array = array.sorted{$0.compare($1, options: .numeric) == .orderedAscending}
источник