Saya memiliki array Contact
objek:
var contacts:[Contact] = [Contact]()
Kelas kontak:
Class Contact:NSOBject {
var firstName:String!
var lastName:String!
}
Dan saya ingin mengurutkan array itu lastName
kemudian firstName
jika beberapa kontak mendapatkan hal yang sama lastName
.
Saya dapat mengurutkan berdasarkan salah satu kriteria tersebut, tetapi tidak keduanya.
contacts.sortInPlace({$0.lastName < $1.lastName})
Bagaimana saya bisa menambahkan lebih banyak kriteria untuk mengurutkan array ini?
Contact
mungkin tidak boleh mewarisi dariNSObject
, 2)Contact
mungkin harus berupa struct, dan 3)firstName
danlastName
mungkin tidak boleh secara implisit membuka bungkus opsional.Jawaban:
Pikirkan tentang arti "mengurutkan menurut beberapa kriteria". Artinya dua objek dibandingkan terlebih dahulu dengan satu kriteria. Kemudian, jika kriteria tersebut sama, maka ikatan akan diputus oleh kriteria selanjutnya, begitu seterusnya hingga mendapatkan urutan yang diinginkan.
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 } }
Apa yang Anda lihat di sini adalah
Sequence.sorted(by:)
metode , yang berkonsultasi dengan closure yang disediakan untuk menentukan bagaimana elemen dibandingkan.Jika penyortiran Anda akan digunakan di banyak tempat, mungkin lebih baik membuat jenis Anda sesuai dengan
Comparable
protokol . Dengan begitu, Anda bisa menggunakanSequence.sorted()
metode , yang berkonsultasi dengan implementasiComparable.<(_:_:)
operator Anda untuk menentukan bagaimana elemen dibandingkan. Dengan cara ini, Anda bisa menyortir setiapSequence
dariContact
s tanpa harus menduplikasi kode penyortiran.sumber
else
tubuh harus antara{ ... }
lain kode tidak kompilasi.sort
vs.sortInPlace
lihat di sini . Seperti yang terlihat di bawah ini , ini jauh lebih modularsortInPlace
TIDAK lagi tersedia di Swift 3, Anda harus menggunakannyasort()
.sort()
akan mengubah array itu sendiri. Juga ada fungsi baru bernamasorted()
yang akan mengembalikan array yang diurutkan==
bukanlah ide yang baik. Ini hanya berfungsi untuk 2 properti. Lebih dari itu, dan Anda mulai mengulangi diri Anda sendiri dengan banyak ekspresi boolean yang rumitMenggunakan tupel untuk melakukan perbandingan beberapa kriteria
Cara yang sangat sederhana untuk melakukan pengurutan menurut beberapa kriteria (yaitu mengurutkan berdasarkan satu perbandingan, dan jika setara, kemudian dengan perbandingan lain) adalah dengan menggunakan tupel , karena operator
<
dan>
memiliki kelebihan beban untuk mereka yang melakukan perbandingan leksikografik./// 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
Sebagai contoh:
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") // ]
Ini akan membandingkan properti elemen
lastName
terlebih dahulu. Jika tidak sama, maka urutan sortir akan didasarkan pada<
perbandingan dengannya. Jika mereka adalah sama, maka akan pindah ke pasangan berikutnya elemen dalam tupel, yaitu membandingkanfirstName
sifat.Pustaka standar menyediakan
<
dan>
membebani tupel dengan 2 hingga 6 elemen.Jika Anda menginginkan urutan pengurutan yang berbeda untuk properti yang berbeda, Anda cukup menukar elemen di tupel:
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"), // ]
Ini sekarang akan mengurutkan berdasarkan
lastName
turun, lalufirstName
naik.Mendefinisikan
sort(by:)
kelebihan beban yang membutuhkan banyak predikatTerinspirasi oleh diskusi tentang Menyortir Koleksi dengan
map
closures dan SortDescriptors , opsi lain adalah menentukan kelebihan beban khusussort(by:)
dansorted(by:)
yang berhubungan dengan beberapa predikat - di mana setiap predikat dipertimbangkan secara bergiliran untuk memutuskan urutan elemen.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:
Parameter ini disayangkan, tetapi diperlukan untuk menghindari membuat ambiguitas dengansort(by:)
kelebihan beban yang ada )Ini kemudian memungkinkan kita untuk mengatakan (menggunakan
contacts
array dari sebelumnya):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 // ... )
Meskipun situs panggilan tidak sesingkat varian tupel, Anda mendapatkan kejelasan tambahan dengan apa yang dibandingkan dan dalam urutan apa.
Sesuai dengan
Comparable
Jika Anda akan melakukan perbandingan semacam ini secara teratur, seperti yang disarankan @AMomchilov & @appzYourLife , Anda dapat menyesuaikan diri
Contact
denganComparable
: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) } }
Dan sekarang panggil saja
sort()
untuk urutan naik:contacts.sort()
atau
sort(by: >)
untuk urutan menurun:contacts.sort(by: >)
Mendefinisikan urutan kustom dalam tipe bertingkat
Jika Anda memiliki susunan urutan lain yang ingin Anda gunakan, Anda dapat menentukannya dalam tipe bertingkat:
extension Contact { enum Comparison { static let firstLastAscending: (Contact, Contact) -> Bool = { return ($0.firstName, $0.lastName) < ($1.firstName, $1.lastName) } } }
lalu panggil sebagai:
contacts.sort(by: Contact.Comparison.firstLastAscending)
sumber
contacts.sort { ($0.lastName, $0.firstName) < ($1.lastName, $1.firstName) }
Membantu. Terima kasihcontacts.sort { ($0.lastName ?? "", $0.firstName ?? "") < ($1.lastName ?? "", $1.firstName ?? "") }
.""
dibandingkan dengan string lain (itu datang sebelum string yang tidak kosong). Ini agak tersirat, agak ajaib, dan tidak fleksibel jika Anda ingin kata-katanil
itu muncul di akhir daftar. Saya sarankan Anda melihatnilComparator
fungsi saya stackoverflow.com/a/44808567/3141234Pendekatan sederhana lainnya untuk menyortir dengan 2 kriteria ditunjukkan di bawah ini.
Periksa bidang pertama, dalam hal ini
lastName
, jika tidak sama, urutkan berdasarkanlastName
, jikalastName
sama, lalu urutkan menurut bidang kedua, dalam kasus inifirstName
.contacts.sort { $0.lastName == $1.lastName ? $0.firstName < $1.firstName : $0.lastName < $1.lastName }
sumber
Satu hal yang tidak dapat dilakukan oleh pengurutan leksikografis seperti yang dijelaskan oleh @Hamish adalah menangani arah pengurutan yang berbeda, misalnya urutkan berdasarkan kolom pertama yang menurun, kolom berikutnya naik, dll.
Saya membuat posting blog tentang cara melakukannya di Swift 3 dan menjaga kodenya tetap sederhana dan mudah dibaca.
Anda dapat menemukannya di sini:
http://master-method.com/index.php/2016/11/23/sort-a-sequence-ie-arrays-of-objects-by-multiple-properties-in-swift-3/Anda juga dapat menemukan repositori GitHub dengan kode di sini:
https://github.com/jallauca/SortByMultipleFieldsSwift.playground
Inti dari semuanya, katakanlah, jika Anda memiliki daftar lokasi, Anda akan dapat melakukan ini:
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 )
sumber
Pertanyaan ini sudah memiliki banyak jawaban bagus, tetapi saya ingin menunjuk ke sebuah artikel - Urutkan Deskriptor di Swift . Kami memiliki beberapa cara untuk melakukan penyortiran beberapa kriteria.
Menggunakan NSSortDescriptor, cara ini memiliki beberapa keterbatasan, objek harus berupa kelas dan mewarisi dari 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), ]
Di sini, misalnya, kami ingin mengurutkan menurut nama belakang, lalu nama depan, terakhir menurut tahun lahir. Dan kami ingin melakukannya secara tidak peka huruf besar dan menggunakan lokal pengguna.
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)]
Menggunakan cara cepat dalam mengurutkan dengan nama belakang / nama depan. Cara ini harus bekerja dengan kedua class / struct. Namun, kami tidak mengurutkan berdasarkan yearOfBirth di sini.
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)]
Cara cepat untuk menggunakan NSSortDescriptor. Ini menggunakan konsep bahwa 'fungsi adalah tipe kelas satu'. SortDescriptor adalah tipe fungsi, mengambil dua nilai, mengembalikan bool. Katakanlah sortByFirstName kita mengambil dua parameter ($ 0, $ 1) dan membandingkan nama depannya. Fungsi gabungan membutuhkan banyak SortDescriptors, membandingkan semuanya dan memberi perintah.
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)]
Ini bagus karena Anda dapat menggunakannya dengan struct dan class, Anda bahkan dapat memperluasnya untuk membandingkan dengan nils.
Tetap saja, membaca artikel asli sangat disarankan. Ini memiliki lebih banyak detail dan dijelaskan dengan baik.
sumber
Saya akan merekomendasikan menggunakan solusi tupel Hamish karena tidak memerlukan kode tambahan.
Jika Anda menginginkan sesuatu yang berperilaku seperti
if
pernyataan tetapi menyederhanakan logika bercabang, Anda dapat menggunakan solusi ini, yang memungkinkan Anda melakukan hal berikut:animals.sort { return comparisons( compare($0.family, $1.family, ascending: false), compare($0.name, $1.name)) }
Berikut fungsi yang memungkinkan Anda melakukan ini:
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 }
Jika Anda ingin mengujinya, Anda dapat menggunakan kode tambahan ini:
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), ]
Perbedaan utama dari solusi Jamie adalah bahwa akses ke properti didefinisikan secara inline daripada sebagai metode statis / instance di kelas. Misalnya,
$0.family
bukanAnimal.familyCompare
. Dan ascending / descending dikontrol oleh parameter alih-alih operator yang kelebihan beban. Solusi Jamie menambahkan ekstensi pada Array sedangkan solusi saya menggunakan metodesort
/sorted
bawaan tetapi membutuhkan dua tambahan untuk didefinisikan:compare
dancomparisons
.Demi kelengkapan, berikut perbandingan solusi saya dengan solusi tupel Hamish . Untuk mendemonstrasikan, saya akan menggunakan contoh liar di mana kami ingin mengurutkan orang berdasarkan
(name, address, profileViews)
solusi Hamish akan mengevaluasi masing-masing dari 6 nilai properti tepat satu kali sebelum perbandingan dimulai. Ini mungkin tidak diinginkan atau mungkin tidak diinginkan. Misalnya, dengan asumsiprofileViews
panggilan jaringan mahal, kami mungkin ingin menghindari panggilanprofileViews
kecuali itu benar-benar diperlukan. Solusi saya akan menghindari evaluasiprofileViews
sampai$0.name == $1.name
dan$0.address == $1.address
. Namun, ketika itu benar-benar mengevaluasiprofileViews
kemungkinan akan mengevaluasi lebih dari sekali.sumber
Bagaimana tentang:
contacts.sort() { [$0.last, $0.first].lexicographicalCompare([$1.last, $1.first]) }
sumber
lexicographicallyPrecedes
mengharuskan semua tipe dalam array menjadi sama. Misalnya[String, String]
. Apa yang mungkin diinginkan OP adalah mencampur dan mencocokkan jenis:[String, Int, Bool]
sehingga mereka bisa melakukannya[$0.first, $0.age, $0.isActive]
.yang berfungsi untuk array [String] saya di Swift 3 dan tampaknya di Swift 4 tidak masalah
array = array.sorted{$0.compare($1, options: .numeric) == .orderedAscending}
sumber