Bagaimana saya bisa menentukan jumlah kasus dalam Swift enum?
(Saya ingin menghindari penghitungan secara manual melalui semua nilai , atau menggunakan " trik enum_count " yang lama jika memungkinkan.)
Bagaimana saya bisa menentukan jumlah kasus dalam Swift enum?
(Saya ingin menghindari penghitungan secara manual melalui semua nilai , atau menggunakan " trik enum_count " yang lama jika memungkinkan.)
Jawaban:
Pada Swift 4.2 (Xcode 10) Anda dapat mendeklarasikan kesesuaian dengan CaseIterable
protokol, ini berfungsi untuk semua enumerasi tanpa nilai terkait:
enum Stuff: CaseIterable {
case first
case second
case third
case forth
}
Jumlah kasus sekarang hanya diperoleh dengan
print(Stuff.allCases.count) // 4
Untuk informasi lebih lanjut, lihat
Saya memiliki posting blog yang lebih detail tentang hal ini, tetapi selama tipe mentah enum Anda adalah bilangan bulat, Anda dapat menambahkan hitungan dengan cara ini:
enum Reindeer: Int {
case Dasher, Dancer, Prancer, Vixen, Comet, Cupid, Donner, Blitzen
case Rudolph
static let count: Int = {
var max: Int = 0
while let _ = Reindeer(rawValue: max) { max += 1 }
return max
}()
}
case A=1, B=3
?
enum
memiliki Int
nilai mentah yang Anda lupa sebutkan: Swift enum dengan nilai mentah Int tidak harus mulai dari 0 (meskipun itu adalah perilaku default) dan nilai mentahnya dapat berubah-ubah, mereka tidak memiliki bertambah 1 (meskipun itu adalah perilaku default).
Pembaruan Xcode 10
Mengadopsi CaseIterable
protokol dalam enum, ia menyediakan allCases
properti statis yang berisi semua kasus enum sebagai a Collection
. Cukup gunakan count
propertinya untuk mengetahui berapa banyak kasus yang dimiliki oleh enum.
Lihat jawaban Martin sebagai contoh (dan lebih baik jawabnya daripada jawabanku)
Peringatan : metode di bawah ini sepertinya tidak berfungsi lagi.
Saya tidak mengetahui adanya metode generik untuk menghitung jumlah kasus enum. Namun saya perhatikan bahwa hashValue
properti dari kasus enum adalah tambahan, mulai dari nol, dan dengan urutan yang ditentukan oleh urutan di mana kasus tersebut dinyatakan. Jadi, hash enum terakhir ditambah satu sesuai dengan jumlah kasus.
Misalnya dengan enum ini:
enum Test {
case ONE
case TWO
case THREE
case FOUR
static var count: Int { return Test.FOUR.hashValue + 1}
}
count
mengembalikan 4.
Saya tidak bisa mengatakan apakah itu aturan atau jika itu akan berubah di masa depan, jadi gunakan dengan risiko Anda sendiri :)
hashValues
hal-hal ini; yang kami tahu hanyalah beberapa nilai unik acak - dapat berubah dengan sangat mudah di masa mendatang tergantung pada beberapa detail implementasi kompiler; tetapi secara keseluruhan, kurangnya fungsionalitas penghitungan bawaan mengganggu.
case ONE = 0
, Anda dapat menggantinya hashValue
dengan rawValue
.
static var count = 4
daripada meninggalkan takdir Anda dalam nasib implementasi Swift
Saya mendefinisikan protokol yang dapat digunakan kembali yang secara otomatis melakukan perhitungan kasus berdasarkan pendekatan yang diposting oleh Nate Cook.
protocol CaseCountable {
static var caseCount: Int { get }
}
extension CaseCountable where Self: RawRepresentable, Self.RawValue == Int {
internal static var caseCount: Int {
var count = 0
while let _ = Self(rawValue: count) {
count += 1
}
return count
}
}
Maka saya dapat menggunakan kembali protokol ini misalnya sebagai berikut:
enum Planet : Int, CaseCountable {
case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}
//..
print(Planet.caseCount)
count++
ke count+=1
karena ++
notasi akan dihapus di Swift 3
static var caseCount: Int { get }
? mengapa perlu untuk itu static func
?
case A=1, B=3
?
0
dan tidak memiliki celah.
Buat array allValues statis seperti yang ditunjukkan dalam jawaban ini
enum ProductCategory : String {
case Washers = "washers", Dryers = "dryers", Toasters = "toasters"
static let allValues = [Washers, Dryers, Toasters]
}
...
let count = ProductCategory.allValues.count
Ini juga membantu ketika Anda ingin menghitung nilai, dan berfungsi untuk semua tipe Enum
static let count = allValues.count
. Maka Anda bisa menjadikannya allValues
pribadi jika diinginkan.
Jika implementasi tidak memiliki masalah terhadap penggunaan integer enums, Anda bisa menambahkan nilai anggota tambahan yang dipanggil Count
untuk mewakili jumlah anggota dalam enum - lihat contoh di bawah ini:
enum TableViewSections : Int {
case Watchlist
case AddButton
case Count
}
Sekarang Anda bisa mendapatkan jumlah anggota dalam enum dengan menelepon, TableViewSections.Count.rawValue
yang akan mengembalikan 2 untuk contoh di atas.
Saat Anda menangani enum dalam pernyataan pergantian, pastikan untuk melontarkan pernyataan kegagalan saat bertemu dengan Count
anggota di mana Anda tidak mengharapkannya:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let currentSection: TableViewSections = TableViewSections.init(rawValue:section)!
switch(currentSection) {
case .Watchlist:
return watchlist.count
case .AddButton:
return 1
case .Count:
assert(false, "Invalid table view section!")
}
}
Fungsi semacam ini mampu mengembalikan jumlah enum Anda.
Swift 2 :
func enumCount<T: Hashable>(_: T.Type) -> Int {
var i = 1
while (withUnsafePointer(&i) { UnsafePointer<T>($0).memory }).hashValue != 0 {
i += 1
}
return i
}
Swift 3 :
func enumCount<T: Hashable>(_: T.Type) -> Int {
var i = 1
while (withUnsafePointer(to: &i, {
return $0.withMemoryRebound(to: T.self, capacity: 1, { return $0.pointee })
}).hashValue != 0) {
i += 1
}
return i
}
enum
adalah juga Hashable
dari jenis yang sama.
String Enum dengan Index
enum eEventTabType : String {
case Search = "SEARCH"
case Inbox = "INBOX"
case Accepted = "ACCEPTED"
case Saved = "SAVED"
case Declined = "DECLINED"
case Organized = "ORGANIZED"
static let allValues = [Search, Inbox, Accepted, Saved, Declined, Organized]
var index : Int {
return eEventTabType.allValues.indexOf(self)!
}
}
hitung: eEventTabType.allValues.count
indeks: objeEventTabType.index
Nikmati :)
Oh, hei semuanya, bagaimana dengan unit test?
func testEnumCountIsEqualToNumberOfItemsInEnum() {
var max: Int = 0
while let _ = Test(rawValue: max) { max += 1 }
XCTAssert(max == Test.count)
}
Ini dikombinasikan dengan solusi Antonio:
enum Test {
case one
case two
case three
case four
static var count: Int { return Test.four.hashValue + 1}
}
dalam kode utama memberi Anda O (1) plus Anda mendapatkan tes gagal jika seseorang menambahkan enum case five
dan tidak memperbarui implementasi count
.
Fungsi ini bergantung pada 2 perilaku tidak terdokumentasi saat ini (Swift 1.1) enum
:
enum
hanya indeks case
. Jika jumlah kasus dari 2 hingga 256, itu UInt8
.enum
bit-casted dari indeks kasus tidak valid , hashValue
itu0
Jadi gunakan dengan risiko Anda sendiri :)
func enumCaseCount<T:Hashable>(t:T.Type) -> Int {
switch sizeof(t) {
case 0:
return 1
case 1:
for i in 2..<256 {
if unsafeBitCast(UInt8(i), t).hashValue == 0 {
return i
}
}
return 256
case 2:
for i in 257..<65536 {
if unsafeBitCast(UInt16(i), t).hashValue == 0 {
return i
}
}
return 65536
default:
fatalError("too many")
}
}
Pemakaian:
enum Foo:String {
case C000 = "foo"
case C001 = "bar"
case C002 = "baz"
}
enumCaseCount(Foo) // -> 3
Saya menulis ekstensi sederhana yang memberikan semua enum di mana nilai mentah adalah count
properti:
extension RawRepresentable where RawValue: IntegerType {
static var count: Int {
var i: RawValue = 0
while let _ = Self(rawValue: i) {
i = i.successor()
}
return Int(i.toIntMax())
}
}
Sayangnya itu memberikan count
properti ke OptionSetType
tempat itu tidak akan berfungsi dengan baik, jadi di sini adalah versi lain yang membutuhkan kesesuaian eksplisit dengan CaseCountable
protokol untuk enum yang mana kasus yang ingin Anda hitung:
protocol CaseCountable: RawRepresentable {}
extension CaseCountable where RawValue: IntegerType {
static var count: Int {
var i: RawValue = 0
while let _ = Self(rawValue: i) {
i = i.successor()
}
return Int(i.toIntMax())
}
}
Ini sangat mirip dengan pendekatan yang diposting oleh Tom Pelaia, tetapi bekerja dengan semua tipe integer.
Tentu saja, ini tidak dinamis tetapi untuk banyak kegunaan Anda dapat bertahan dengan var statis ditambahkan ke Enum Anda
static var count: Int{ return 7 }
dan kemudian menggunakannya sebagai EnumName.count
enum EnumNameType: Int {
case first
case second
case third
static var count: Int { return EnumNameType.third.rawValue + 1 }
}
print(EnumNameType.count) //3
ATAU
enum EnumNameType: Int {
case first
case second
case third
case count
}
print(EnumNameType.count.rawValue) //3
* Pada Swift 4.2 (Xcode 10) dapat menggunakan:
enum EnumNameType: CaseIterable {
case first
case second
case third
}
print(EnumNameType.allCases.count) //3
Untuk kasus penggunaan saya, dalam basis kode di mana banyak orang dapat menambahkan kunci ke enum, dan semua kasus ini harus tersedia di properti allKeys, penting agar allKeys divalidasi terhadap kunci di enum. Ini untuk menghindari seseorang lupa menambahkan kunci mereka ke daftar semua kunci.Mencocokkan jumlah array allKeys (pertama kali dibuat sebagai set untuk menghindari dupes) terhadap jumlah kunci dalam enum memastikan bahwa semuanya ada.
Beberapa jawaban di atas menunjukkan cara untuk mencapai ini di Swift 2 tetapi tidak ada yang berhasil di Swift 3 . Ini adalah versi berformat Swift 3 :
static func enumCount<T: Hashable>(_ t: T.Type) -> Int {
var i = 1
while (withUnsafePointer(to: &i) {
$0.withMemoryRebound(to:t.self, capacity:1) { $0.pointee.hashValue != 0 }
}) {
i += 1
}
return i
}
static var allKeys: [YourEnumTypeHere] {
var enumSize = enumCount(YourEnumTypeHere.self)
let keys: Set<YourEnumTypeHere> = [.all, .your, .cases, .here]
guard keys.count == enumSize else {
fatalError("Missmatch between allKeys(\(keys.count)) and actual keys(\(enumSize)) in enum.")
}
return Array(keys)
}
Tergantung pada kasus penggunaan Anda, Anda mungkin ingin menjalankan tes dalam pengembangan untuk menghindari overhead menggunakan allKeys pada setiap permintaan
Mengapa Anda membuatnya begitu rumit? Penghitung SIMPLEST dari Int enum adalah untuk menambahkan:
case Count
Pada akhirnya. Dan ... biola - sekarang Anda memiliki hitungan - cepat dan sederhana
0
dan tidak memiliki celah dalam urutan.
Jika Anda tidak ingin mendasarkan kode Anda di enum terakhir, Anda dapat membuat fungsi ini di dalam enum Anda.
func getNumberOfItems() -> Int {
var i:Int = 0
var exit:Bool = false
while !exit {
if let menuIndex = MenuIndex(rawValue: i) {
i++
}else{
exit = true
}
}
return i
}
Versi Swift 3 yang bekerja dengan Int
enum tipe:
protocol CaseCountable: RawRepresentable {}
extension CaseCountable where RawValue == Int {
static var count: RawValue {
var i: RawValue = 0
while let _ = Self(rawValue: i) { i += 1 }
return i
}
}
Kredit: Berdasarkan jawaban oleh bzz dan Nate Cook.
Generic IntegerType
(dalam Swift 3 diganti namanya menjadi Integer
) tidak didukung, karena ini adalah tipe generik yang sangat terfragmentasi yang tidak memiliki banyak fungsi. successor
sudah tidak tersedia lagi dengan Swift 3.
Ketahuilah bahwa komentar dari Code Commander ke Nate Cooks masih valid:
Meskipun bagus karena Anda tidak perlu meng-hardcode suatu nilai, ini akan membuat instantiate setiap nilai enum setiap kali dipanggil. Itu adalah O (n) bukan O (1).
Sejauh yang saya tahu saat ini tidak ada solusi saat menggunakan ini sebagai ekstensi protokol (dan tidak menerapkan di setiap enum seperti Nate Cook lakukan) karena properti tersimpan statis tidak didukung dalam tipe generik.
Bagaimanapun, untuk enum kecil ini seharusnya tidak menjadi masalah. Kasus penggunaan yang khas akan menjadi section.count
untuk UITableViews
sebagaimana telah disebutkan oleh Zorayr.
Memperluas jawaban Matthieu Riegler, ini adalah solusi untuk Swift 3 yang tidak memerlukan penggunaan obat generik, dan dapat dengan mudah dipanggil menggunakan tipe enum dengan EnumType.elementsCount
:
extension RawRepresentable where Self: Hashable {
// Returns the number of elements in a RawRepresentable data structure
static var elementsCount: Int {
var i = 1
while (withUnsafePointer(to: &i, {
return $0.withMemoryRebound(to: self, capacity: 1, { return
$0.pointee })
}).hashValue != 0) {
i += 1
}
return i
}
Saya memecahkan masalah ini untuk diri saya sendiri dengan membuat protokol (EnumIntArray) dan fungsi utilitas global (enumIntArray) yang membuatnya sangat mudah untuk menambahkan variabel "Semua" ke enum apa pun (menggunakan swift 1.2). Variabel "semua" akan berisi larik semua elemen di enum sehingga Anda dapat menggunakan all.count untuk penghitungan
Ini hanya bekerja dengan enum yang menggunakan nilai mentah dari tipe Int tetapi mungkin dapat memberikan beberapa inspirasi untuk jenis lainnya.
Ini juga membahas masalah "kesenjangan dalam penomoran" dan "waktu yang berlebihan untuk mengulangi" yang telah saya baca di atas dan di tempat lain.
Idenya adalah untuk menambahkan protokol EnumIntArray ke enum Anda dan kemudian mendefinisikan variabel statis "semua" dengan memanggil fungsi enumIntArray dan menyediakannya dengan elemen pertama (dan yang terakhir jika ada kesenjangan dalam penomoran).
Karena variabel statis hanya diinisialisasi sekali, biaya overhead untuk melewati semua nilai mentah hanya mengenai program Anda sekali.
contoh (tanpa celah):
enum Animals:Int, EnumIntArray
{
case Cat=1, Dog, Rabbit, Chicken, Cow
static var all = enumIntArray(Animals.Cat)
}
contoh (dengan celah):
enum Animals:Int, EnumIntArray
{
case Cat = 1, Dog,
case Rabbit = 10, Chicken, Cow
static var all = enumIntArray(Animals.Cat, Animals.Cow)
}
Berikut kode yang mengimplementasikannya:
protocol EnumIntArray
{
init?(rawValue:Int)
var rawValue:Int { get }
}
func enumIntArray<T:EnumIntArray>(firstValue:T, _ lastValue:T? = nil) -> [T]
{
var result:[T] = []
var rawValue = firstValue.rawValue
while true
{
if let enumValue = T(rawValue:rawValue++)
{ result.append(enumValue) }
else if lastValue == nil
{ break }
if lastValue != nil
&& rawValue > lastValue!.rawValue
{ break }
}
return result
}
Atau Anda bisa mendefinisikan bagian _count
luar enum, dan melampirkannya secara statis:
let _count: Int = {
var max: Int = 0
while let _ = EnumName(rawValue: max) { max += 1 }
return max
}()
enum EnumName: Int {
case val0 = 0
case val1
static let count = _count
}
Dengan begitu tidak peduli berapa banyak enum yang Anda buat, itu hanya akan pernah dibuat sekali.
(hapus jawaban ini jika static
melakukannya)
Metode berikut ini berasal dari CoreKit dan mirip dengan jawaban yang disarankan beberapa orang lain. Ini bekerja dengan Swift 4.
public protocol EnumCollection: Hashable {
static func cases() -> AnySequence<Self>
static var allValues: [Self] { get }
}
public extension EnumCollection {
public static func cases() -> AnySequence<Self> {
return AnySequence { () -> AnyIterator<Self> in
var raw = 0
return AnyIterator {
let current: Self = withUnsafePointer(to: &raw) { $0.withMemoryRebound(to: self, capacity: 1) { $0.pointee } }
guard current.hashValue == raw else {
return nil
}
raw += 1
return current
}
}
}
public static var allValues: [Self] {
return Array(self.cases())
}
}
enum Weekdays: String, EnumCollection {
case sunday, monday, tuesday, wednesday, thursday, friday, saturday
}
Maka Anda hanya perlu menelepon saja Weekdays.allValues.count
.
enum WeekDays : String , CaseIterable
{
case monday = "Mon"
case tuesday = "Tue"
case wednesday = "Wed"
case thursday = "Thu"
case friday = "Fri"
case saturday = "Sat"
case sunday = "Sun"
}
var weekdays = WeekDays.AllCases()
print("\(weekdays.count)")
struct HashableSequence<T: Hashable>: SequenceType {
func generate() -> AnyGenerator<T> {
var i = 0
return AnyGenerator {
let next = withUnsafePointer(&i) { UnsafePointer<T>($0).memory }
if next.hashValue == i {
i += 1
return next
}
return nil
}
}
}
extension Hashable {
static func enumCases() -> Array<Self> {
return Array(HashableSequence())
}
static var enumCount: Int {
return enumCases().enumCount
}
}
enum E {
case A
case B
case C
}
E.enumCases() // [A, B, C]
E.enumCount // 3
tapi hati-hati dengan penggunaan pada jenis non-enum. Beberapa solusi bisa berupa:
struct HashableSequence<T: Hashable>: SequenceType {
func generate() -> AnyGenerator<T> {
var i = 0
return AnyGenerator {
guard sizeof(T) == 1 else {
return nil
}
let next = withUnsafePointer(&i) { UnsafePointer<T>($0).memory }
if next.hashValue == i {
i += 1
return next
}
return nil
}
}
}
extension Hashable {
static func enumCases() -> Array<Self> {
return Array(HashableSequence())
}
static var enumCount: Int {
return enumCases().count
}
}
enum E {
case A
case B
case C
}
Bool.enumCases() // [false, true]
Bool.enumCount // 2
String.enumCases() // []
String.enumCount // 0
Int.enumCases() // []
Int.enumCount // 0
E.enumCases() // [A, B, C]
E.enumCount // 4
Itu dapat menggunakan konstanta statis yang berisi nilai terakhir dari enumerasi plus satu.
enum Color : Int {
case Red, Orange, Yellow, Green, Cyan, Blue, Purple
static let count: Int = Color.Purple.rawValue + 1
func toUIColor() -> UIColor{
switch self {
case .Red:
return UIColor.redColor()
case .Orange:
return UIColor.orangeColor()
case .Yellow:
return UIColor.yellowColor()
case .Green:
return UIColor.greenColor()
case .Cyan:
return UIColor.cyanColor()
case .Blue:
return UIColor.blueColor()
case .Purple:
return UIColor.redColor()
}
}
}
Ini kecil, tapi saya pikir solusi O (1) yang lebih baik adalah sebagai berikut ( HANYA jika enum Anda Int
mulai dari x, dll.):
enum Test : Int {
case ONE = 1
case TWO
case THREE
case FOUR // if you later need to add additional enums add above COUNT so COUNT is always the last enum value
case COUNT
static var count: Int { return Test.COUNT.rawValue } // note if your enum starts at 0, some other number, etc. you'll need to add on to the raw value the differential
}
Jawaban yang dipilih saat ini saya masih percaya adalah jawaban terbaik untuk semua enum, kecuali Anda bekerja dengan itu Int
maka saya merekomendasikan solusi ini.
guard
menentang COUNT
dan melempar kesalahan, mengembalikan false, dll. Untuk menyelesaikan kekhawatiran Anda tentang representasi tipe.