Untuk mengurutkan array Swift lokal berdasarkan beberapa field, bandingkan setiap kunci sesuai urutan prioritas dan lanjutkan hanya jika nilainya sama. Tentukan nilai cadangan opsional secara terpisah, lalu tetapkan posisi untuk nilai yang tidak tersedia. Contoh ini mengurutkan label bentuk sintetis untuk daftar desktop Mac.
Tetapkan aturan pengurutan sebelum menulis closure
Misalkan sebuah daftar desktop berisi label bentuk dengan nomor grup, posisi pilihan opsional, posisi cadangan opsional, dan judul. Tugasnya adalah menghasilkan satu urutan tampilan yang dapat diprediksi dari nilai-nilai dalam memori ini.
Dua keputusan memerlukan aturan terpisah. Nilai cadangan memilih nilai yang mewakili satu item: gunakan posisi pilihan jika tersedia, jika tidak gunakan posisi cadangan. Pemecah seri membandingkan field lain hanya setelah dua item memiliki posisi efektif yang sama. Operator nil-coalescing Swift, ??, mendukung keputusan pertama dengan memilih nilai yang dibungkus atau nilai cadangan. Basic Operators — Nil-Coalescing Operator
Gunakan aturan eksplisit berikut untuk contoh ini:
| Prioritas | Kunci | Aturan |
|---|---|---|
| 1 | Grup | Nomor lebih kecil diurutkan lebih dulu |
| 2 | Posisi efektif | preferredPosition ?? backupPosition |
| 3 | Penempatan posisi yang tidak tersedia | Posisi yang tidak tersedia ditempatkan setelah posisi yang tersedia dalam setiap grup |
| 4 | Judul | Perbandingan string Swift menaik saat posisi sama |
| 5 | Pengenal | Pengenal lebih kecil diurutkan lebih dulu jika semua kunci sebelumnya sama |
Penempatan posisi yang tidak tersedia merupakan bagian dari perbandingan posisi, bukan field terpisah yang mengesampingkan grup. Item di grup nol tetap mendahului item di grup satu meskipun item pertama tidak memiliki posisi.
Perbandingan judul sengaja menggunakan pengurutan string biasa Swift untuk perhitungan sintetis ini. Pertahankan pilihan itu jika nanti Anda mengganti closure dengan pembanding Foundation.
Tulis pengurutan multi-field lengkap dengan penanganan nil yang eksplisit
Program lengkap berikut menggunakan pustaka standar Swift dan tidak memerlukan impor. Jalankan di lingkungan yang mendukung Swift pada Mac Anda. Semua input ditentukan di dalam program; perhitungan tidak menggunakan input eksternal atau penyimpanan persisten. Operasi pengurutan menghasilkan array baru melalui sorted(by:). Dokumentasi sorted(by:) dari Apple
struct ShapeItem {
let id: Int
let group: Int
let preferredPosition: Int?
let backupPosition: Int?
let title: String
var effectivePosition: Int? {
preferredPosition ?? backupPosition
}
}
func comesBefore(_ lhs: ShapeItem, _ rhs: ShapeItem) -> Bool {
if lhs.group != rhs.group {
return lhs.group < rhs.group
}
switch (lhs.effectivePosition, rhs.effectivePosition) {
case let (.some(left), .some(right)):
if left != right {
return left < right
}
case (.some(_), .none):
return true
case (.none, .some(_)):
return false
case (.none, .none):
break
}
if lhs.title != rhs.title {
return lhs.title < rhs.title
}
return lhs.id < rhs.id
}
let items = [
ShapeItem(id: 10, group: 1, preferredPosition: nil,
backupPosition: 2, title: "Circle"),
ShapeItem(id: 20, group: 1, preferredPosition: 1,
backupPosition: 9, title: "Square"),
ShapeItem(id: 30, group: 1, preferredPosition: nil,
backupPosition: nil, title: "Arc"),
ShapeItem(id: 40, group: 1, preferredPosition: 2,
backupPosition: 0, title: "Arc"),
ShapeItem(id: 50, group: 0, preferredPosition: nil,
backupPosition: nil, title: "Dot"),
ShapeItem(id: 60, group: 1, preferredPosition: 2,
backupPosition: nil, title: "Arc"),
ShapeItem(id: 70, group: 1, preferredPosition: 0,
backupPosition: 8, title: "Zero")
]
let ordered = items.sorted(by: comesBefore)
let expectedIDs = [50, 70, 20, 40, 60, 10, 30]
precondition(ordered.map(\.id) == expectedIDs)
precondition(ordered.count == items.count)
precondition(items.reversed().sorted(by: comesBefore).map(\.id)
== expectedIDs)
print(ordered.map(\.id))
Output yang diharapkan:
[50, 70, 20, 40, 60, 10, 30]
Program dan urutan yang diharapkan telah diperiksa, tetapi tidak dijalankan di sini karena lingkungan riset tidak memiliki kompiler Swift. Prasyarat yang disertakan memungkinkan Anda memeriksa hasilnya saat menjalankannya secara lokal.
Alasan setiap sampel berada pada posisi tersebut
Item 50 berada di urutan pertama karena grup nol didahulukan sebelum posisi dipertimbangkan. Item 70 kemudian menunjukkan bahwa nol adalah posisi pilihan yang valid: nilai cadangannya, delapan, tidak digunakan. Item 20 menggunakan posisi pilihannya, yaitu satu.
Item 40, 60, dan 10 semuanya memiliki posisi efektif dua. Kedua judul Arc mendahului Circle, dan pengenal menyelesaikan seri yang tersisa antara 40 dan 60. Item 30 berada di urutan terakhir karena kedua field posisi tidak tersedia di grup satu.
Ini juga menjawab pertanyaan umum tentang nilai cadangan: item 40 tidak menggunakan posisi cadangannya, yaitu nol, ketika posisi pilihannya sama dengan item lain. Berdasarkan aturan ini, nilai cadangan hanya menggantikan nilai pilihan yang tidak tersedia. Jika nilai cadangan juga harus memecahkan seri, diperlukan aturan perbandingan tambahan.
Pastikan pembanding tetap valid saat nilainya sama
Swift mengharuskan predikat pengurutan membentuk urutan lemah ketat. Secara praktis, suatu item tidak boleh mendahului dirinya sendiri, keputusan pengurutan harus tetap konsisten di seluruh perbandingan, dan item-item yang setara harus membentuk kelompok yang konsisten. Apple mendokumentasikan persyaratan ini untuk sorted(by:). Persyaratan predikat
Contoh ini memeriksa apakah kunci sebelumnya berbeda sebelum mengembalikan hasil pengurutannya. Perbandingan mencapai judul hanya jika grup dan posisi efektif sama, lalu mencapai pengenal hanya jika judul juga sama. Jika pengenal dan semua kunci sebelumnya sama, perbandingan ketat terakhir mengembalikan false.
Hindari menggabungkan perbandingan field yang terpisah dengan ||. Satu item bisa memiliki grup lebih awal tetapi posisi lebih akhir; item lain bisa memiliki kombinasi sebaliknya. Predikat berbasis OR dapat menyatakan bahwa masing-masing item harus mendahului yang lain. Penanganan seri secara berurutan mencegah kontradiksi tersebut.
Hindari juga menggunakan <= sebagai perbandingan terakhir: nilai yang sama akan mengembalikan true, sehingga melanggar aturan bahwa suatu item tidak boleh mendahului dirinya sendiri. Pastikan setiap perbandingan hanya bergantung pada nilai yang diberikan dan aturan pengurutan tetap.
Pilih antara seri stabil dan pengenal terakhir
Pengurutan Swift yang terdokumentasi bersifat stabil: elemen yang dianggap setara oleh predikat mempertahankan urutan relatifnya dalam input. Jika perilaku itu yang diinginkan, hilangkan perbandingan pengenal terakhir dan kembalikan false setelah judul sama. Jaminan pengurutan stabil
Contoh ini menggunakan pengenal sintetis unik untuk menyelesaikan seri yang tersisa. Karena itu, membalik urutan input sampel tetap menghasilkan urutan pengenal yang sama. Sifat tersebut berasal dari aturan dan nilai sampel ini. Jika pengenal berulang bersama semua kunci lainnya, item-item tersebut tetap setara dan mempertahankan urutan inputnya.
Nyatakan aturan yang sama dengan pembanding key-path Foundation
Foundation menyediakan KeyPathComparator untuk membandingkan properti dan sorted(using:) untuk menerapkan rangkaian pembanding. Pembanding pertama menentukan urutan utama; pembanding berikutnya memecahkan seri. API ini tersedia mulai macOS 12. KeyPathComparator, sorted(using:)
Untuk versi Foundation yang setara, sediakan dua kunci posisi: peringkat nilai yang tidak tersedia dan posisi numerik. Peringkat menempatkan posisi yang tidak tersedia di urutan terakhir. Setelah peringkatnya sama, nilai pengganti numerik untuk posisi yang tidak tersedia tidak mungkin berbenturan dengan nilai nol yang tersedia, karena item-item tersebut sudah memiliki peringkat berbeda.
Tambahkan ekstensi dan perhitungan berikut setelah program lengkap di atas. Ini adalah contoh lanjutan yang menggunakan definisi ShapeItem, items, dan expectedIDs yang sudah ada. Contoh ini memerlukan macOS 14 atau yang lebih baru karena secara eksplisit membuat ComparableComparator dengan init(order:). Ketersediaan initializer
import Foundation
extension ShapeItem {
var missingPositionRank: Int {
effectivePosition == nil ? 1 : 0
}
var numericPositionKey: Int {
effectivePosition ?? 0
}
}
if #available(macOS 14.0, *) {
let comparators: [KeyPathComparator<ShapeItem>] = [
KeyPathComparator(\ShapeItem.group, order: .forward),
KeyPathComparator(\ShapeItem.missingPositionRank,
order: .forward),
KeyPathComparator(\ShapeItem.numericPositionKey,
order: .forward),
KeyPathComparator(
\ShapeItem.title,
comparator: ComparableComparator<String>(order: .forward)
),
KeyPathComparator(\ShapeItem.id, order: .forward)
]
let foundationOrdered = items.sorted(using: comparators)
precondition(foundationOrdered.map(\.id) == expectedIDs)
print(foundationOrdered.map(\.id))
} else {
print(items.sorted(by: comesBefore).map(\.id))
}
Pembanding judul yang eksplisit mempertahankan pilihan sebelumnya untuk menggunakan pengurutan string Swift. Foundation mendokumentasikan ComparableComparator sebagai pembanding yang menggunakan implementasi Comparable dari tipe yang dibandingkan, dan KeyPathComparator menerima pembanding properti yang diberikan. ComparableComparator, Initializer pembanding key-path
Untuk satu susunan tetap, closure membuat setiap cabang terlihat jelas. Array pembanding berguna jika Anda ingin urutan kunci yang didukung direpresentasikan sebagai nilai. Dalam kedua pendekatan, pertahankan pemilihan nilai cadangan di properti terhitung model agar kedua implementasi menggunakan definisi yang sama.
Sesuaikan aturan tanpa mengubah maknanya
Untuk mengurutkan posisi secara menurun sambil tetap menempatkan posisi yang tidak tersedia di urutan terakhir, ubah hanya perbandingan antara dua posisi yang tersedia menjadi left > right. Dalam versi Foundation, balikkan hanya numericPositionKey; biarkan missingPositionRank tetap maju. Membalik seluruh hasil juga akan membalik grup, judul, pengenal, dan penempatan nilai yang tidak tersedia.
Jika string opsional menjadi kunci cadangan, tentukan secara terpisah apakah string kosong dianggap tidak tersedia. Nilai nil-coalescing merespons nil; operator ini tidak mengganti string kosong yang tersedia. Terapkan aturan normalisasi tambahan sebelum pengurutan, dan gunakan kunci efektif yang sama di seluruh proses. Perilaku nil-coalescing
Jaga perhitungan kecil ini tetap sinkron. Panduan konkurensi Swift membedakan pengurutan sinkron biasa dari titik penangguhan, dan predikat sorted(by:) yang terdokumentasi bersifat sinkron. Jika aplikasi memerlukan nilai dari pekerjaan asinkron, selesaikan pengambilan nilai tersebut sebelum memanggil pengurut ini. Menambahkan await di dalam closure perbandingan bukan perluasan signature yang didukung. Concurrency, Signature sorted(by:)
Sebelum menyesuaikan contoh ini untuk daftar desktop lain, pertahankan pemeriksaan untuk kunci pilihan yang tidak tersedia, kedua kunci posisi yang tidak tersedia, nilai nol yang valid, posisi efektif yang sama, serta field tampilan yang seluruhnya sama. Tentukan apakah seri terakhir harus mempertahankan urutan input atau menggunakan pengenal. Pilihan tersebut memberikan aturan pengurutan yang jelas untuk daftar dan tetap mudah dipahami pada kedua implementasi.
Sumber
- sorted(by:) — Apple
- Basic Operators — Swift Project
- KeyPathComparator — Apple
- sorted(using:) — Apple
- ComparableComparator — Apple
- init(order:) — Apple
- init(_:comparator:) — Apple
- Concurrency — Swift Project