¿Cómo puedo determinar el número de casos en una enumeración Swift?
(Me gustaría evitar enumerar manualmente todos los valores , o usar el viejo " truco enum_count " si es posible).
¿Cómo puedo determinar el número de casos en una enumeración Swift?
(Me gustaría evitar enumerar manualmente todos los valores , o usar el viejo " truco enum_count " si es posible).
Respuestas:
A partir de Swift 4.2 (Xcode 10) puede declarar conformidad con el CaseIterable
protocolo, esto funciona para todas las enumeraciones sin valores asociados:
enum Stuff: CaseIterable {
case first
case second
case third
case forth
}
El número de casos ahora se obtiene simplemente con
print(Stuff.allCases.count) // 4
Para más información, ver
Tengo una publicación de blog que entra en más detalles sobre esto, pero siempre que el tipo sin formato de su enumeración sea un entero, puede agregar un recuento de esta manera:
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
tener un Int
valor bruto que olvidó mencionar: las enumeraciones rápidas con valores brutos Int no tienen que comenzar desde 0 (aunque ese es el comportamiento predeterminado) y sus valores brutos pueden ser arbitrarios, no tienen incrementar en 1 (aunque ese es el comportamiento predeterminado).
Actualización de Xcode 10
Adopte el CaseIterable
protocolo en la enumeración, proporciona una allCases
propiedad estática que contiene todos los casos de enumeración como a Collection
. Simplemente use su count
propiedad para saber cuántos casos tiene la enumeración.
Vea la respuesta de Martin como ejemplo (y vote sus respuestas en lugar de las mías)
Advertencia : el siguiente método ya no parece funcionar.
No conozco ningún método genérico para contar el número de casos enum. Sin embargo, he notado que la hashValue
propiedad de los casos enum es incremental, comenzando desde cero y con el orden determinado por el orden en que se declaran los casos. Entonces, el hash de la última enumeración más uno corresponde al número de casos.
Por ejemplo con esta enumeración:
enum Test {
case ONE
case TWO
case THREE
case FOUR
static var count: Int { return Test.FOUR.hashValue + 1}
}
count
devuelve 4.
No puedo decir si eso es una regla o si alguna vez cambiará en el futuro, así que úselo bajo su propio riesgo :)
hashValues
estas cosas; todo lo que sabemos es que es un valor único aleatorio: podría cambiar muy fácilmente en el futuro dependiendo de algún detalle de implementación del compilador; pero en general, la falta de funcionalidad de conteo incorporada es inquietante.
case ONE = 0
, puede reemplazar hashValue
con rawValue
.
static var count = 4
lugar de dejar su destino en el destino de futuras implementaciones de Swift
Defino un protocolo reutilizable que realiza automáticamente el recuento de casos según el enfoque publicado por 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
}
}
Entonces puedo reutilizar este protocolo, por ejemplo, de la siguiente manera:
enum Planet : Int, CaseCountable {
case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}
//..
print(Planet.caseCount)
count++
de count+=1
puesto ++
de notación se eliminará en Swift 3
static var caseCount: Int { get }
? ¿Por qué la necesidad de la static func
?
case A=1, B=3
?
0
y no tengan brechas.
Cree una matriz estática de todos los valores como se muestra en esta respuesta
enum ProductCategory : String {
case Washers = "washers", Dryers = "dryers", Toasters = "toasters"
static let allValues = [Washers, Dryers, Toasters]
}
...
let count = ProductCategory.allValues.count
Esto también es útil cuando desea enumerar los valores, y funciona para todos los tipos de Enum
static let count = allValues.count
. Entonces puedes hacer el allValues
privado si lo deseas.
Si la implementación no tiene nada en contra del uso de enumeraciones enteras, puede agregar un valor de miembro adicional llamado Count
para representar el número de miembros en la enumeración; consulte el siguiente ejemplo:
enum TableViewSections : Int {
case Watchlist
case AddButton
case Count
}
Ahora puede obtener el número de miembros en la enumeración llamando, TableViewSections.Count.rawValue
lo que devolverá 2 para el ejemplo anterior.
Cuando maneja la enumeración en una declaración de cambio, asegúrese de lanzar un error de aserción cuando encuentre al Count
miembro donde no lo espera:
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!")
}
}
Este tipo de función puede devolver el recuento de su enumeración.
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
es del mismo tipo. Hashable
Enum de cadena con índice
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)!
}
}
cuenta: eEventTabType.allValues.count
índice: objeEventTabType.index
Disfruta :)
Oh, hola a todos, ¿qué pasa con las pruebas unitarias?
func testEnumCountIsEqualToNumberOfItemsInEnum() {
var max: Int = 0
while let _ = Test(rawValue: max) { max += 1 }
XCTAssert(max == Test.count)
}
Esto combinado con la solución de Antonio:
enum Test {
case one
case two
case three
case four
static var count: Int { return Test.four.hashValue + 1}
}
en el código principal le da O (1) además de obtener una prueba fallida si alguien agrega un caso de enumeración five
y no actualiza la implementación de count
.
Esta función se basa en 2 comportamientos de corriente no documentada (Swift 1.1) enum
:
enum
es solo un índice de case
. Si el recuento de casos es de 2 a 256, es UInt8
.enum
bit fue lanzado desde un índice de caso inválido , hashValue
es0
Así que úselo bajo su propio riesgo :)
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
Escribí una extensión simple que proporciona todas las enumeraciones donde el valor bruto es una count
propiedad entera :
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())
}
}
Desafortunadamente, le da a la count
propiedad OptionSetType
donde no funcionará correctamente, por lo que aquí hay otra versión que requiere conformidad explícita con el CaseCountable
protocolo para cualquier enumeración de los casos que desea contar:
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())
}
}
Es muy similar al enfoque publicado por Tom Pelaia, pero funciona con todos los tipos enteros.
Por supuesto, no es dinámico, pero para muchos usos puede obtener una var estática agregada a su Enum
static var count: Int{ return 7 }
y luego úsalo como 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
* En Swift 4.2 (Xcode 10) puede usar:
enum EnumNameType: CaseIterable {
case first
case second
case third
}
print(EnumNameType.allCases.count) //3
Para mi caso de uso, en una base de código donde varias personas podrían estar agregando claves a una enumeración, y estos casos deberían estar disponibles en la propiedad allKeys, es importante que todas las claves sean validadas contra las claves de la enumeración. Esto es para evitar que alguien se olvide de agregar su clave a la lista de todas las claves. Hacer coincidir el recuento de la matriz allKeys (creada primero como un conjunto para evitar engaños) con el número de claves en la enumeración asegura que todos estén presentes.
Algunas de las respuestas anteriores muestran la forma de lograr esto en Swift 2, pero ninguna funciona en Swift 3 . Aquí está la versión formateada de 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)
}
Dependiendo de su caso de uso, es posible que desee ejecutar la prueba en desarrollo para evitar la sobrecarga de usar allKeys en cada solicitud
¿Por qué lo haces todo tan complejo? El contador SIMPLEST de Int enum es agregar:
case Count
En el final. Y ... viola - ahora tienes la cuenta - rápido y simple
0
y no tengan espacios en la secuencia.
Si no desea basar su código en la última enumeración, puede crear esta función dentro de su enumeración.
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 versión de Swift 3 que funciona con Int
enumeraciones de tipo:
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
}
}
Créditos: Basado en las respuestas de bzz y Nate Cook.
Genérico IntegerType
(en Swift 3 renombrado a Integer
) no es compatible, ya que es un tipo genérico muy fragmentado que carece de muchas funciones. successor
ya no está disponible con Swift 3.
Tenga en cuenta que el comentario de Code Commander a la respuesta de Nate Cooks sigue siendo válido:
Si bien es bueno porque no necesita codificar un valor, esto creará una instancia de cada valor de enumeración cada vez que se llame. Eso es O (n) en lugar de O (1).
Hasta donde sé, actualmente no hay una solución alternativa cuando se usa esto como extensión de protocolo (y no se implementa en cada enumeración como lo hizo Nate Cook) debido a que las propiedades almacenadas estáticas no son compatibles con los tipos genéricos.
De todos modos, para enumeraciones pequeñas esto no debería ser un problema. Un caso de uso típico sería el section.count
de UITableViews
como ya se ha mencionado por Zorayr.
Ampliando la respuesta de Matthieu Riegler, esta es una solución para Swift 3 que no requiere el uso de genéricos, y se puede llamar fácilmente usando el 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
}
Resolví este problema por mí mismo creando un protocolo (EnumIntArray) y una función de utilidad global (enumIntArray) que hace que sea muy fácil agregar una variable "All" a cualquier enumeración (usando swift 1.2). La variable "all" contendrá una matriz de todos los elementos en la enumeración para que pueda usar all.count para el conteo
Solo funciona con enumeraciones que usan valores sin procesar del tipo Int, pero quizás puede proporcionar alguna inspiración para otros tipos.
También aborda los problemas de "brecha en la numeración" y "tiempo excesivo para repetir" que he leído anteriormente y en otros lugares.
La idea es agregar el protocolo EnumIntArray a su enumeración y luego definir una variable estática "all" llamando a la función enumIntArray y proporcionarle el primer elemento (y el último si hay espacios en la numeración).
Debido a que la variable estática solo se inicializa una vez, la sobrecarga de pasar por todos los valores sin procesar solo afecta a su programa una vez.
ejemplo (sin espacios):
enum Animals:Int, EnumIntArray
{
case Cat=1, Dog, Rabbit, Chicken, Cow
static var all = enumIntArray(Animals.Cat)
}
ejemplo (con huecos):
enum Animals:Int, EnumIntArray
{
case Cat = 1, Dog,
case Rabbit = 10, Chicken, Cow
static var all = enumIntArray(Animals.Cat, Animals.Cow)
}
Aquí está el código que 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
}
O simplemente puede definir el _count
exterior de la enumeración y adjuntarlo estáticamente:
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
}
De esa manera, no importa cuántas enumeraciones cree, solo se creará una vez.
(elimine esta respuesta si static
hace eso)
El siguiente método proviene de CoreKit y es similar a las respuestas que otros han sugerido. Esto funciona 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
}
Entonces solo necesitas llamar 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
pero tenga cuidado con el uso en tipos que no sean enum. Algunas soluciones podrían ser:
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
Puede usar una constante estática que contiene el último valor de la enumeración más 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()
}
}
}
Esto es menor, pero creo que una mejor solución O (1) sería la siguiente ( SOLO si su enumeración Int
comienza en x, etc.):
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 respuesta seleccionada actual que todavía creo que es la mejor respuesta para todas las enumeraciones, a menos que esté trabajando Int
, le recomiendo esta solución.
guard
está en contra COUNT
y arroja un error, devuelve falso, etc. para resolver su preocupación por la representación de tipos.