¿Cómo obtengo el recuento de una enumeración Swift?


Respuestas:


173

A partir de Swift 4.2 (Xcode 10) puede declarar conformidad con el CaseIterableprotocolo, 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


1
En la última versión de swift, su error de lanzamiento "Tipo 'DAFFlow' no se ajusta al protocolo 'RawRepresentable'". ¿Por qué me obliga a seguir eso? ¿Alguna idea?
Sábado

@Satyam: ¿Qué es DAFFlow?
Martin R

perdón, olvidé mencionar que "DAFFlow 'es una enumeración simple que no se hereda de ningún otro protocolo.
Sábado

1
Esta es la mejor solución, pero solo por claridad: los desarrolladores de Apple solo podrán comenzar a usar esto una vez que Xcode 10 (y, por lo tanto, Swift 4.2) salga de la versión beta (probablemente alrededor del 14 de septiembre de 2018).
JosephH

1
@DaniSpringer: Encontrará los detalles sangrientos en github.com/apple/swift-evolution/blob/master/proposals/… . Pero generalmente no necesita ese tipo explícitamente, debido a la inferencia de tipo automática del compilador.
Martin R

143

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
    }()
}

16
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). :(
Code Commander

44
Esta es una buena solución para int contiguas. Prefiero una ligera modificación. Convierta la propiedad de conteo estático en un método estático countCases () y asígnelo a un constante caseCount estático que es vago y mejora el rendimiento con llamadas repetidas.
Tom Pelaia

2
@ShamsAhmed: Convierte la var calculada en una estática.
Nate Cook

3
¿Qué pasa si pierdes algún valor en la enumeración? por ejemplo case A=1, B=3?
Sasho

2
Hay 2 suposiciones además de enumtener un Intvalor 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).
Dávid Pásztor

90

Actualización de Xcode 10

Adopte el CaseIterableprotocolo en la enumeración, proporciona una allCasespropiedad estática que contiene todos los casos de enumeración como a Collection. Simplemente use su countpropiedad 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 hashValuepropiedad 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 :)


48
Vive por la característica indocumentada, muere por la característica indocumentada. ¡Me gusta!
Nate Cook el

9
Realmente no deberíamos confiar en hashValuesestas 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.
Zorayr

16
Si no le importa establecer explícitamente case ONE = 0, puede reemplazar hashValuecon rawValue.
Kevin Qi

3
La preocupación aquí es el uso de una propiedad no documentada de hashValue, por lo que mi sugerencia es usar una propiedad documentada de rawValue.
Kevin Qi

77
Ya ha codificado el hecho de que la constante es el valor más alto, mejor y más seguro para usar algo así en static var count = 4lugar de dejar su destino en el destino de futuras implementaciones de Swift
Dale

72

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)

1
Agradable y elegante, debería ser la respuesta aceptada en mi humilde opinión
shannoga

1
tal vez es mejor con el cambio count++de count+=1puesto ++de notación se eliminará en Swift 3
Aladin

1
¿no podría hacerse lo mismo con solo static var caseCount: Int { get }? ¿Por qué la necesidad de la static func?
pxpgraphics

¿Qué pasa si pierdes algún valor en la enumeración? por ejemplo case A=1, B=3?
Sasho

1
@ Sasho, entonces no funcionará. Esto requiere que sus casos comiencen 0y no tengan brechas.
NRitH

35

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


Aunque no es tan elegante como la solución de extensiones y es muy manual, creo que esta es la más útil, ya que ofrece mucho más que contar. Le da un orden de valores y una lista de todos los valores.
Nader Eloshaiker

2
También puede agregar el recuento a la enumeración haciendo static let count = allValues.count. Entonces puedes hacer el allValuesprivado si lo deseas.
ThomasW

15

Si la implementación no tiene nada en contra del uso de enumeraciones enteras, puede agregar un valor de miembro adicional llamado Countpara 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.rawValuelo 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 Countmiembro 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!")
  }
}

Me gusta esa solución, ya que cambia el recuento automáticamente al agregar más valores de enumeración. Sin embargo, tenga en cuenta que esto solo funciona cuando los RawValues ​​de la enumeración comienzan con 0.
joern

2
De acuerdo, hay dos restricciones: debe ser una enumeración de enteros y debe comenzar en cero y continuar gradualmente.
Zorayr

3
Pensé que el punto de las enumeraciones más poderosas de Swift era que no tendríamos que usar los mismos trucos que usamos en Objective-C: /
pkamb

14

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
   }

3
Esto ya no funciona para Swift 3. Tratando de resolver la implementación adecuada, pero quedando vacío
Cody Winton

Esto será muy difícil de depurar si la dirección de memoria inmediatamente adyacente al final de la tambiénenum es del mismo tipo. Hashable
NRitH

10

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 :)


10

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 fivey no actualiza la implementación de count.


7

Esta función se basa en 2 comportamientos de corriente no documentada (Swift 1.1) enum:

  • El diseño de memoria de enumes solo un índice de case. Si el recuento de casos es de 2 a 256, es UInt8.
  • Si el enumbit fue lanzado desde un índice de caso inválido , hashValuees0

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

En su lanzamiento y aplicación adhoc CRASH
HotJard

Esto funciona en el simulador pero no en un dispositivo real de 64 bits.
Daniel Nord

5

Escribí una extensión simple que proporciona todas las enumeraciones donde el valor bruto es una countpropiedad 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 countpropiedad OptionSetTypedonde no funcionará correctamente, por lo que aquí hay otra versión que requiere conformidad explícita con el CaseCountableprotocolo 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.


4

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


3
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

2

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


2

¿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


1
Esto a) agrega un caso de enumeración extraño yb) no funcionará si el tipo sin procesar de la enumeración no es Int.
Robert Atkins

Esto en realidad no es una mala respuesta. Sin embargo, al igual que la respuesta de @Tom Pelaia anterior, requiere que los valores brutos comiencen 0y no tengan espacios en la secuencia.
NRitH

1

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
}

1

Una versión de Swift 3 que funciona con Intenumeraciones 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. successorya 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.countde UITableViewscomo ya se ha mencionado por Zorayr.


1

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
}

0

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   
}

0

O simplemente puede definir el _countexterior 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 statichace eso)


0

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.


0
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)")

-1
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

-1

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()
        }
    }
}

-3

Esto es menor, pero creo que una mejor solución O (1) sería la siguiente ( SOLO si su enumeración Intcomienza 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.


3
Agregar un valor a su enumeración que en realidad no representa el tipo de enumeración es un mal olor de código. Me resulta difícil incluso justificar la inclusión de un "TODO" o "NINGUNO", aunque a veces puede ser tentador. Incluir un "COUNT" solo para hackear este problema es muy desagradable.
Michael Peterson

1
Stinky? Si quieres llamarlo así de seguro. Performant? Si. Depende del desarrollador decidir los pros y los contras. Esta es en realidad la misma respuesta a la respuesta de Zorayr anterior, donde entra en más detalles al respecto, y la nueva respuesta aceptada también es similar. Pero hasta que Swift agregue una API para ello; Esto es lo que algunos de nosotros hemos decidido usar. Puede agregar una función que valida el valor de enumeración que guardestá en contra COUNTy arroja un error, devuelve falso, etc. para resolver su preocupación por la representación de tipos.
Drmorgan
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.