Como obtenho a contagem de uma enumeração Swift?

Respostas:

172

No Swift 4.2 (Xcode 10), você pode declarar conformidade com o CaseIterableprotocolo, isso funciona para todas as enumerações sem valores associados:

enum Stuff: CaseIterable {
    case first
    case second
    case third
    case forth
}

O número de casos agora é simplesmente obtido com

print(Stuff.allCases.count) // 4

Para mais informações, veja

Martin R
fonte
1
Na versão mais recente do swift, seu erro de lançamento "Tipo 'DAFFlow' não está em conformidade com o protocolo 'RawRepresentable'". Por que está me forçando a seguir isso? Qualquer ideia?
Satyam
@Satyam: O que é DAFFlow?
Martin R
desculpe, eu esqueci de mencionar que "DAFFlow' é uma enumeração simples que não herdam a partir de qualquer outro protocolo.
Satyam
1
Esta é a melhor solução, mas apenas para maior clareza - os desenvolvedores da Apple só poderão realmente começar a usá-lo quando o Xcode 10 (e, portanto, o Swift 4.2) sair da versão beta (provavelmente por volta de 14 de setembro de 2018).
Josephh
1
@DaniSpringer: Você encontrará os detalhes sangrentos em github.com/apple/swift-evolution/blob/master/proposals/… . Mas geralmente você não precisa desse tipo explicitamente, devido à inferência automática de tipo do compilador.
Martin R
143

Eu tenho um post de blog que entra em mais detalhes sobre isso, mas desde que o tipo bruto da sua enum seja um número inteiro, você pode adicionar uma contagem dessa maneira:

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
    }()
}
Nate Cook
fonte
16
Embora agradável, porque você não precisa codificar um valor, isso instancia cada valor de enum sempre que for chamado. Isso é O (n) em vez de O (1). :(
Code Commander
4
Esta é uma boa solução para Int contíguo. Eu prefiro uma ligeira modificação. Transforme a propriedade static count em um método estático countCases () e atribua-o a uma constante estática caseCount, que é lenta e melhora o desempenho com chamadas repetidas.
Tom Pelaia 15/07/2015
2
@ ShamsAhmed: converteu o var computado em um estático.
Nate Cook
3
E se você perder algum valor na enumeração? por exemplo case A=1, B=3?
Sasho
2
Além de enumter um Intvalor bruto que você esqueceu de mencionar, há duas suposições : os swum enum com valores brutos Int não precisam começar de 0 (mesmo que esse seja o comportamento padrão) e seus valores brutos possam ser arbitrários, eles não têm para aumentar em 1 (mesmo que esse seja o comportamento padrão).
Dávid Pásztor
90

Atualização do Xcode 10

Adote o CaseIterableprotocolo no enum, ele fornece uma allCasespropriedade estática que contém todos os casos de enum como a Collection. Basta usar sua countpropriedade para saber quantos casos o enum possui.

Veja a resposta de Martin para um exemplo (e vote mais nas respostas dele do que nas minhas)


Aviso : o método abaixo parece não funcionar mais.

Não conheço nenhum método genérico para contar o número de casos enum. Notei, no entanto, que a hashValuepropriedade dos casos enum é incremental, começando de zero e com a ordem determinada pela ordem em que os casos são declarados. Portanto, o hash do último enum mais um corresponde ao número de casos.

Por exemplo, com esta enumeração:

enum Test {
    case ONE
    case TWO
    case THREE
    case FOUR

    static var count: Int { return Test.FOUR.hashValue + 1}
}

count retorna 4.

Não posso dizer se isso é uma regra ou se alguma vez vai mudar no futuro, então use por sua conta e risco :)

Antonio
fonte
48
Ao vivo pelo recurso não documentado, morra pelo recurso não documentado. Eu gosto disso!
Nate Cook
9
Nós realmente não devemos confiar hashValuesnessas coisas; tudo o que sabemos é que é algum valor exclusivo aleatório - pode mudar com muita facilidade no futuro, dependendo de alguns detalhes de implementação do compilador; mas, no geral, a falta de funcionalidade integrada de contagem é perturbadora.
Zorayr
16
Se você não se importar com a configuração explícita case ONE = 0, poderá substituí-lo hashValuepor rawValue.
Kevin Qi
3
a preocupação aqui é o uso de uma propriedade não documentada de hashValue, portanto, minha sugestão é usar uma propriedade documentada de rawValue.
Kevin Qi
7
Você já codificado o fato de que constante é o valor mais alto, melhor e mais seguro usar algo assim como static var count = 4em vez de deixar o seu destino no destino de futuras implementações de Swift
Dale
72

Defino um protocolo reutilizável que executa automaticamente a contagem de casos com base na abordagem postada 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
    }
}

Então eu posso reutilizar este protocolo, por exemplo, da seguinte maneira:

enum Planet : Int, CaseCountable {
    case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}
//..
print(Planet.caseCount)
Tom Pelaia
fonte
1
Agradável e elegante, deve ser a resposta IMHO aceito
shannoga
1
talvez seja melhor para a mudança count++de count+=1uma vez ++notação será removido em Swift 3
Aladin
1
o mesmo não poderia ser feito apenas static var caseCount: Int { get }? por que a necessidade de static func?
Pxpgraphics # 6/16
E se você perder algum valor na enumeração? por exemplo case A=1, B=3?
Sasho
1
@ Sasho, então não vai funcionar. Isso requer que seus casos iniciem 0e não tenham lacunas.
NRitH 15/11
35

Crie uma matriz estática allValues, como mostrado nesta resposta

enum ProductCategory : String {
     case Washers = "washers", Dryers = "dryers", Toasters = "toasters"

     static let allValues = [Washers, Dryers, Toasters]
}

...

let count = ProductCategory.allValues.count

Isso também é útil quando você deseja enumerar os valores e funciona para todos os tipos de Enum.

david72
fonte
Embora não seja tão elegante quanto a solução de extensões e muito manual, acredito que seja a mais útil, pois oferece muito mais do que conta. Ele fornece a ordem dos valores e uma lista de todos os valores.
Nader Eloshaiker
2
Você também pode adicionar a contagem à enumeração fazendo static let count = allValues.count. Então você pode tornar o allValuesprivado, se desejar.
Thomasw
15

Se a implementação não tiver nada contra o uso de enumerações inteiras, você poderá adicionar um valor de membro extra chamado Countpara representar o número de membros na enumeração - veja o exemplo abaixo:

enum TableViewSections : Int {
  case Watchlist
  case AddButton
  case Count
}

Agora você pode obter o número de membros na enum chamando, TableViewSections.Count.rawValue que retornará 2 para o exemplo acima.

Ao manipular o enum em uma instrução switch, lembre-se de lançar uma falha de asserção ao encontrar o Countmembro em que você não o 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!")
  }
}
Zorayr
fonte
Eu gosto dessa solução, pois altera a contagem automaticamente ao adicionar mais valores de enumeração. No entanto, tenha em mente que isso só funciona quando os rawValues do enum começar com 0.
joern
2
Concordo, existem duas restrições: deve ser um enum inteiro e deve começar em zero e continuar incrementalmente.
Zorayr
3
Eu pensei que todo o ponto de enums mais poderosas de Swift foi que não teria que usar os mesmos hacks que usamos em Objective-C: /
pkamb
14

Esse tipo de função é capaz de retornar a contagem de sua enumeração.

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
   }
Matthieu Riegler
fonte
3
Isto já não funciona para Swift 3. tentando descobrir a implementação adequada, mas chegando vazio
Cody Winton
Isso será muito difícil de depurar se o endereço de memória imediatamente adjacente ao final do tambémenum for do mesmo tipo. Hashable
NRitH 15/11
10

String Enum com 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)!
    }
}

contagem: eEventTabType.allValues.count

índice: objeEventTabType.index

Aproveitar :)

kalpesh jetani
fonte
10

Oh, ei pessoal, e os testes de unidade?

func testEnumCountIsEqualToNumberOfItemsInEnum() {

    var max: Int = 0
    while let _ = Test(rawValue: max) { max += 1 }

    XCTAssert(max == Test.count)
}

Isso combinado com a solução de Antonio:

enum Test {

    case one
    case two
    case three
    case four

    static var count: Int { return Test.four.hashValue + 1}
}

no código principal fornece O (1) e você obtém um teste com falha se alguém adicionar um caso de enum fivee não atualizar a implementação de count.

buildsucededed
fonte
7

Esta função depende do comportamento de 2 correntes não documentadas (Swift 1.1) enum:

  • O layout da memória de enumé apenas um índice de case. Se a contagem de casos for de 2 a 256, éUInt8 .
  • Se o enumbit foi convertido a partir de um índice de caso inválido , hashValueé0

Portanto, use por sua conta e risco :)

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
rintaro
fonte
Na versão adhoc e aplicação adhoc CRASH
HotJard
Isso funciona no simulador, mas não em um dispositivo real de 64 bits.
Daniel Nord
5

Eu escrevi uma extensão simples que fornece todas as enumerações em que o valor bruto é uma countpropriedade inteira :

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

Infelizmente, ele fornece a countpropriedade para OptionSetTypeonde não funcionará corretamente, então aqui está outra versão que requer conformidade explícita com o CaseCountableprotocolo para qualquer enumeração de quais casos você deseja 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())
    }
}

É muito semelhante à abordagem postada por Tom Pelaia, mas funciona com todos os tipos de números inteiros.

bzz
fonte
4

Claro, não é dinâmico, mas para muitos usos, você pode conviver com uma var estática adicionada ao seu Enum

static var count: Int{ return 7 }

e depois use-o como EnumName.count

Ian Dundas
fonte
3
enum EnumNameType: Int {
    case first
    case second
    case third

    static var count: Int { return EnumNameType.third.rawValue + 1 }
}

print(EnumNameType.count) //3

OU

enum EnumNameType: Int {
    case first
    case second
    case third
    case count
}

print(EnumNameType.count.rawValue) //3

* No Swift 4.2 (Xcode 10) pode usar:

enum EnumNameType: CaseIterable {
    case first
    case second
    case third
}

print(EnumNameType.allCases.count) //3
Lê Cường
fonte
2

Para o meu caso de uso, em uma base de código em que várias pessoas podem adicionar chaves a uma enum, e esses casos devem estar disponíveis na propriedade allKeys, é importante que todas as chaves sejam validadas com relação às chaves na enum. Isso evita que alguém se esqueça de adicionar sua chave à lista de todas as chaves.A correspondência da contagem da matriz allKeys (criada primeiro como um conjunto para evitar enganos) com o número de chaves na enumeração garante que todas elas estejam presentes.

Algumas das respostas acima mostram o caminho para conseguir isso no Swift 2, mas nenhuma funciona no Swift 3 . Aqui está a versão formatada do 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)
}

Dependendo do seu caso de uso, convém executar o teste em desenvolvimento para evitar a sobrecarga do uso de allKeys em cada solicitação

Chris Mitchelmore
fonte
2

Por que você torna tudo tão complexo? O contador SIMPLEST do Int enum é adicionar:

case Count

No final. E ... viola - agora você tem a contagem - rápido e simples

Dimitar Marinov
fonte
1
Isso a) adiciona um caso de enum estranho eb) não funcionará se o tipo bruto da enum for diferente de Int.
Robert Atkins
Isso realmente não é uma resposta ruim. Como a resposta de @Tom Pelaia acima, no entanto, ele requer que os valores brutos comecem 0e não tenham lacunas na sequência.
NRitH 15/11
1

Se você não quiser basear seu código na última enumeração, poderá criar esta função dentro da enumeração.

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
}
Gabriel Araujo
fonte
1

Uma versão do Swift 3 trabalhando com Intenumerações 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: Com base nas respostas de bzz e Nate Cook.

O genérico IntegerType(no Swift 3 renomeado para Integer) não é suportado, pois é um tipo genérico fortemente fragmentado que carece de muitas funções. successornão está mais disponível no Swift 3.

Esteja ciente de que o comentário da resposta do Code Commander a Nate Cooks ainda é válido:

Embora seja legal porque você não precisa codificar um valor, isso instancia cada valor de enum sempre que for chamado. Isso é O (n) em vez de O (1).

Até onde eu sei, atualmente não há solução alternativa ao usá-lo como extensão de protocolo (e não implementar em cada enum como o Nate Cook) devido a propriedades armazenadas estáticas que não são suportadas em tipos genéricos.

De qualquer forma, para pequenas enumerações, isso não deve ser problema. Um caso de uso típico seria o section.countpara, UITableViewscomo já mencionado por Zorayr.

Frederik Winkelsdorf
fonte
1

Estendendo a resposta de Matthieu Riegler, esta é uma solução para o Swift 3 que não requer o uso de genéricos e pode ser facilmente chamada usando o tipo de enum com 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
}
matsoftware
fonte
0

Resolvi esse problema criando um protocolo (EnumIntArray) e uma função de utilitário global (enumIntArray) que facilitam a adição de uma variável "All" a qualquer enum (usando o swift 1.2). A variável "all" conterá uma matriz de todos os elementos na enumeração, para que você possa usar all.count para a contagem

Ele funciona apenas com enumerações que usam valores brutos do tipo Int, mas talvez possa fornecer alguma inspiração para outros tipos.

Ele também aborda a "lacuna na numeração" e o "tempo excessivo para iterar" os problemas que li acima e em outros lugares.

A idéia é adicionar o protocolo EnumIntArray à sua enum e definir uma variável estática "all" chamando a função enumIntArray e fornecer o primeiro elemento (e o último se houver lacunas na numeração).

Como a variável estática é inicializada apenas uma vez, a sobrecarga de passar por todos os valores brutos apenas atinge seu programa uma vez.

exemplo (sem lacunas):

enum Animals:Int, EnumIntArray
{ 
  case Cat=1, Dog, Rabbit, Chicken, Cow
  static var all = enumIntArray(Animals.Cat)
}

exemplo (com lacunas):

enum Animals:Int, EnumIntArray
{ 
  case Cat    = 1,  Dog, 
  case Rabbit = 10, Chicken, Cow
  static var all = enumIntArray(Animals.Cat, Animals.Cow)
}

Aqui está o código que o 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   
}
Alain T.
fonte
0

Ou você pode simplesmente definir o _countexterior da enumeração e anexá-lo estaticamente:

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
}

Dessa forma, não importa quantas enums você crie, ele será criado apenas uma vez.

(exclua esta resposta se staticfizer isso)

AT
fonte
0

O método a seguir vem do CoreKit e é semelhante às respostas sugeridas por outras pessoas. Isso funciona com o 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
}

Então você só precisa ligar Weekdays.allValues.count.

ThomasW
fonte
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)")
Nupur Sharma
fonte
-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

mas tenha cuidado com o uso em tipos não enum. Algumas soluções alternativas podem 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
JMI
fonte
-1

Ele pode usar uma constante estática que contém o último valor da enumeração mais um.

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()
        }
    }
}
93sauu
fonte
-3

Isso é pequeno, mas acho que uma solução O (1) melhor seria a seguinte ( SOMENTE se sua enumeração estiver Intcomeçando em 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 
}

A resposta selecionada atualmente ainda acredito que seja a melhor para todas as enumerações, a menos que você esteja trabalhando com Intisso, recomendo esta solução.

Drmorgan
fonte
3
Adicionar um valor à sua enumeração que na verdade não representa o tipo da enumeração é um mau cheiro para o código. Acho difícil justificar a inclusão de um "TUDO" ou "NENHUM", mesmo que às vezes seja tentador. A inclusão de uma "COUNT" apenas para solucionar esse problema é muito fedida.
Michael Peterson
1
Fedido? Se você quiser chamar assim, com certeza. Desempenho? Sim. Cabe ao desenvolvedor decidir os prós e contras. Esta é realmente a mesma resposta da resposta de Zorayr acima, onde ele entra em mais detalhes sobre ela, e a nova resposta aceita também é semelhante. Mas até que o veloz adicione uma API; é isso que alguns de nós decidimos usar. Você pode adicionar uma função que valide o valor de enum que guardé contra COUNTe gera um erro, retorna falso, etc. para resolver sua preocupação com a representação de tipos.
Drmorgan