Come posso determinare il numero di casi in una enumerazione Swift?
(Vorrei evitare di enumerare manualmente tutti i valori o, se possibile , usare il vecchio " trucco enum_count ").
Come posso determinare il numero di casi in una enumerazione Swift?
(Vorrei evitare di enumerare manualmente tutti i valori o, se possibile , usare il vecchio " trucco enum_count ").
Risposte:
A partire da Swift 4.2 (Xcode 10) è possibile dichiarare la conformità al CaseIterable
protocollo, questo funziona per tutte le enumerazioni senza valori associati:
enum Stuff: CaseIterable {
case first
case second
case third
case forth
}
Il numero di casi è ora ottenuto semplicemente con
print(Stuff.allCases.count) // 4
Per ulteriori informazioni, vedere
Ho un post sul blog che approfondisce questo aspetto, ma fintanto che il tipo raw del tuo enum è un numero intero, puoi aggiungere un conteggio in questo modo:
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
avere un Int
valore non elaborato che hai dimenticato di menzionare: le enum di Swift con valori non elaborati Int non devono iniziare da 0 (anche se si tratta del comportamento predefinito) e i loro valori non elaborati possono essere arbitrari, non hanno incrementare di 1 (anche se questo è il comportamento predefinito).
Aggiornamento di Xcode 10
Adotta il CaseIterable
protocollo nell'enum, fornisce una allCases
proprietà statica che contiene tutti i casi enum come a Collection
. Basta usare la sua count
proprietà per sapere quanti casi ha l'enum.
Vedi la risposta di Martin per un esempio (e vota le sue risposte piuttosto che le mie)
Attenzione : il metodo seguente non sembra funzionare più.
Non sono a conoscenza di alcun metodo generico per contare il numero di casi enum. Ho notato, tuttavia, che la hashValue
proprietà dei casi enum è incrementale, a partire da zero e con l'ordine determinato dall'ordine in cui vengono dichiarati i casi. Quindi, l'hash dell'ultimo enum più uno corrisponde al numero di casi.
Ad esempio con questo enum:
enum Test {
case ONE
case TWO
case THREE
case FOUR
static var count: Int { return Test.FOUR.hashValue + 1}
}
count
ritorna 4.
Non posso dire se questa è una regola o se cambierà mai in futuro, quindi usa a tuo rischio e pericolo :)
hashValues
queste cose; tutto ciò che sappiamo è che è un valore univoco casuale - potrebbe cambiare molto facilmente in futuro a seconda di alcuni dettagli di implementazione del compilatore; ma nel complesso, la mancanza di funzionalità di conteggio integrate è inquietante.
case ONE = 0
, puoi sostituirlo hashValue
con rawValue
.
static var count = 4
piuttosto che lasciare il tuo destino nel destino delle future implementazioni di Swift
Definisco un protocollo riutilizzabile che esegue automaticamente il conteggio dei casi in base all'approccio pubblicato da 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
}
}
Quindi posso riutilizzare questo protocollo ad esempio come segue:
enum Planet : Int, CaseCountable {
case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}
//..
print(Planet.caseCount)
count++
a count+=1
poiché la ++
notazione verrà rimossa in Swift 3
static var caseCount: Int { get }
? perché la necessità di static func
?
case A=1, B=3
.
0
e non abbiano lacune.
Crea un array allValues statico come mostrato in questa risposta
enum ProductCategory : String {
case Washers = "washers", Dryers = "dryers", Toasters = "toasters"
static let allValues = [Washers, Dryers, Toasters]
}
...
let count = ProductCategory.allValues.count
Ciò è utile anche quando si desidera enumerare i valori e funziona per tutti i tipi di Enum
static let count = allValues.count
. Quindi, allValues
se lo desideri, puoi rendere privato.
Se l'implementazione non ha nulla contro l'utilizzo di enumerazioni intere, è possibile aggiungere un valore membro aggiuntivo chiamato Count
per rappresentare il numero di membri nell'enum - vedere l'esempio seguente:
enum TableViewSections : Int {
case Watchlist
case AddButton
case Count
}
Ora puoi ottenere il numero di membri nell'enum chiamando, TableViewSections.Count.rawValue
che restituirà 2 per l'esempio sopra.
Quando gestisci l'enum in un'istruzione switch, assicurati di lanciare un errore di asserzione quando incontri il Count
membro dove non te lo aspetti:
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!")
}
}
Questo tipo di funzione è in grado di restituire il conteggio della tua enum.
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
è anche Hashable
dello stesso tipo.
Enum stringa con indice
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)!
}
}
contare : eEventTabType.allValues.count
indice: objeEventTabType.index
Godere :)
Oh ciao a tutti, per quanto riguarda i test unitari?
func testEnumCountIsEqualToNumberOfItemsInEnum() {
var max: Int = 0
while let _ = Test(rawValue: max) { max += 1 }
XCTAssert(max == Test.count)
}
Questo combinato con la soluzione di Antonio:
enum Test {
case one
case two
case three
case four
static var count: Int { return Test.four.hashValue + 1}
}
nel codice principale ti dà O (1) più ottieni un test fallito se qualcuno aggiunge un caso enum five
e non aggiorna l'implementazione di count
.
Questa funzione si basa su 2 comportamenti correnti non documentati (Swift 1.1) enum
:
enum
è solo un indice di case
. Se il conteggio dei casi va da 2 a 256, lo è UInt8
.enum
bit è stato sottoposto a cast da un indice del caso non valido , lo hashValue
è0
Quindi utilizzare a proprio rischio :)
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")
}
}
Uso:
enum Foo:String {
case C000 = "foo"
case C001 = "bar"
case C002 = "baz"
}
enumCaseCount(Foo) // -> 3
Ho scritto una semplice estensione che fornisce a tutti gli enum in cui il valore grezzo è intero una count
proprietà:
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())
}
}
Sfortunatamente dà la count
proprietà a OptionSetType
dove non funzionerà correttamente, quindi ecco un'altra versione che richiede esplicita conformità al CaseCountable
protocollo per qualsiasi enum quali casi vuoi contare:
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())
}
}
È molto simile all'approccio pubblicato da Tom Pelaia, ma funziona con tutti i tipi di numeri interi.
Certo, non è dinamico ma per molti usi puoi cavartela con una var statica aggiunta al tuo Enum
static var count: Int{ return 7 }
e poi usalo come EnumName.count
enum EnumNameType: Int {
case first
case second
case third
static var count: Int { return EnumNameType.third.rawValue + 1 }
}
print(EnumNameType.count) //3
O
enum EnumNameType: Int {
case first
case second
case third
case count
}
print(EnumNameType.count.rawValue) //3
* Su Swift 4.2 (Xcode 10) puoi usare:
enum EnumNameType: CaseIterable {
case first
case second
case third
}
print(EnumNameType.allCases.count) //3
Nel mio caso d'uso, in una base di codice in cui più persone potrebbero aggiungere chiavi a un enum e questi casi dovrebbero essere tutti disponibili nella proprietà allKeys, è importante che allKeys sia validato rispetto alle chiavi dell'enum. Questo per evitare che qualcuno si dimentichi di aggiungere la propria chiave all'elenco di tutte le chiavi. La corrispondenza del conteggio dell'array allKeys (prima creato come set per evitare duplicati) con il numero di chiavi nell'enum assicura che siano tutte presenti.
Alcune delle risposte sopra mostrano il modo per raggiungere questo obiettivo in Swift 2, ma nessuna funziona in Swift 3 . Ecco la versione formattata di 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)
}
A seconda del tuo caso d'uso, potresti voler semplicemente eseguire il test in fase di sviluppo per evitare il sovraccarico di usare allKeys su ogni richiesta
Perché rendi tutto così complesso? Il contatore PIÙ SEMPLICE di Int enum è aggiungere:
case Count
Alla fine. E ... viola - ora hai il conteggio - veloce e semplice
0
e non abbiano lacune nella sequenza.
Se non vuoi basare il tuo codice sull'ultimo enum puoi creare questa funzione all'interno del tuo enum.
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
}
Una versione di Swift 3 che funziona con i Int
tipi enum:
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
}
}
Crediti: basato sulle risposte di bzz e Nate Cook.
Il generico IntegerType
(in Swift 3 rinominato in Integer
) non è supportato, in quanto è un tipo generico fortemente frammentato privo di molte funzioni. successor
non è più disponibile con Swift 3.
Tieni presente che il commento di Code Commander alla risposta di Nate Cooks è ancora valido:
Anche se bello perché non è necessario codificare un valore, questo creerà un'istanza di ogni valore di enum ogni volta che viene chiamato. Questo è O (n) invece di O (1).
Per quanto ne so, al momento non esiste alcuna soluzione alternativa quando si utilizza questo come estensione del protocollo (e non si implementa in ogni enum come ha fatto Nate Cook) a causa delle proprietà memorizzate statiche non supportate nei tipi generici.
Comunque, per piccoli enumerati questo non dovrebbe essere un problema. Un tipico caso d'uso sarebbe il section.count
per UITableViews
come già detto da Zorayr.
Estendendo la risposta di Matthieu Riegler, questa è una soluzione per Swift 3 che non richiede l'uso di generici e può essere facilmente chiamata usando il tipo enum con 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
}
Ho risolto questo problema da solo creando un protocollo (EnumIntArray) e una funzione di utilità globale (enumIntArray) che semplifica l'aggiunta di una variabile "All" a qualsiasi enum (usando swift 1.2). La variabile "all" conterrà un array di tutti gli elementi nell'enum in modo da poter usare all.count per il conteggio
Funziona solo con enum che usano valori grezzi di tipo Int ma forse può fornire qualche ispirazione per altri tipi.
Affronta anche i problemi di "gap nella numerazione" e "tempo eccessivo per iterare" che ho letto sopra e altrove.
L'idea è di aggiungere il protocollo EnumIntArray al tuo enum e quindi definire una variabile statica "all" chiamando la funzione enumIntArray e fornendogli il primo elemento (e l'ultimo se ci sono lacune nella numerazione).
Poiché la variabile statica viene inizializzata solo una volta, il sovraccarico di passare attraverso tutti i valori non elaborati colpisce il programma solo una volta.
esempio (senza lacune):
enum Animals:Int, EnumIntArray
{
case Cat=1, Dog, Rabbit, Chicken, Cow
static var all = enumIntArray(Animals.Cat)
}
esempio (con lacune):
enum Animals:Int, EnumIntArray
{
case Cat = 1, Dog,
case Rabbit = 10, Chicken, Cow
static var all = enumIntArray(Animals.Cat, Animals.Cow)
}
Ecco il codice che lo implementa:
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
}
Oppure puoi semplicemente definire l' _count
esterno dell'enum e collegarlo staticamente:
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
}
In questo modo, indipendentemente dal numero di enum che crei, verrà creato una sola volta.
(cancella questa risposta se lo static
fa)
Il seguente metodo proviene da CoreKit ed è simile alle risposte suggerite da altri. Funziona con 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
}
Quindi devi solo chiamare 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
ma fai attenzione con l'uso su tipi non enumici. Alcune soluzioni alternative potrebbero essere:
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
Può usare una costante statica che contiene l'ultimo valore dell'enumerazione più uno.
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()
}
}
}
Questo è minore, ma penso che una soluzione O (1) migliore sarebbe la seguente ( SOLO se il tuo enum sta Int
iniziando da x, ecc.):
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
}
La risposta attualmente selezionata, credo ancora, sia la migliore risposta per tutti gli enum, a meno che tu non stia lavorando, Int
allora ti consiglio questa soluzione.
guard
è contro COUNT
e genera un errore, restituisce false, ecc. Per risolvere il problema relativo alla rappresentazione dei tipi.