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 CaseIterableprotocollo, 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.
enumavere un Intvalore 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 CaseIterableprotocollo nell'enum, fornisce una allCasesproprietà statica che contiene tutti i casi enum come a Collection. Basta usare la sua countproprietà 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 hashValueproprietà 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 :)
hashValuesqueste 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 hashValuecon rawValue.
static var count = 4piuttosto 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+=1poiché la ++notazione verrà rimossa in Swift 3
static var caseCount: Int { get }? perché la necessità di static func?
case A=1, B=3.
0e 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, allValuesse lo desideri, puoi rendere privato.
Se l'implementazione non ha nulla contro l'utilizzo di enumerazioni intere, è possibile aggiungere un valore membro aggiuntivo chiamato Countper 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.rawValueche restituirà 2 per l'esempio sopra.
Quando gestisci l'enum in un'istruzione switch, assicurati di lanciare un errore di asserzione quando incontri il Countmembro 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 fivee 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.enumbit è stato sottoposto a cast da un indice del caso non valido , lo hashValueè0Quindi 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 countproprietà:
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 countproprietà a OptionSetTypedove non funzionerà correttamente, quindi ecco un'altra versione che richiede esplicita conformità al CaseCountableprotocollo 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
0e 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 Inttipi 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. successornon è 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.countper UITableViewscome 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' _countesterno 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 staticfa)
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 Intiniziando 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, Intallora ti consiglio questa soluzione.
guardè contro COUNTe genera un errore, restituisce false, ecc. Per risolvere il problema relativo alla rappresentazione dei tipi.