Como posso determinar o número de casos em uma enumeração Swift?
(Gostaria de evitar enumerar manualmente todos os valores ou usar o antigo " truque enum_count ", se possível).
Como posso determinar o número de casos em uma enumeração Swift?
(Gostaria de evitar enumerar manualmente todos os valores ou usar o antigo " truque enum_count ", se possível).
Respostas:
No Swift 4.2 (Xcode 10), você pode declarar conformidade com o CaseIterable
protocolo, 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
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
}()
}
case A=1, B=3
?
enum
ter um Int
valor 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).
Atualização do Xcode 10
Adote o CaseIterable
protocolo no enum, ele fornece uma allCases
propriedade estática que contém todos os casos de enum como a Collection
. Basta usar sua count
propriedade 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 hashValue
propriedade 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 :)
hashValues
nessas 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.
case ONE = 0
, poderá substituí-lo hashValue
por rawValue
.
static var count = 4
em vez de deixar o seu destino no destino de futuras implementações de Swift
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)
count++
de count+=1
uma vez ++
notação será removido em Swift 3
static var caseCount: Int { get }
? por que a necessidade de static func
?
case A=1, B=3
?
0
e não tenham lacunas.
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.
static let count = allValues.count
. Então você pode tornar o allValues
privado, se desejar.
Se a implementação não tiver nada contra o uso de enumerações inteiras, você poderá adicionar um valor de membro extra chamado Count
para 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 Count
membro 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!")
}
}
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
}
enum
for do mesmo tipo. Hashable
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 :)
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 five
e não atualizar a implementação de count
.
Esta função depende do comportamento de 2 correntes não documentadas (Swift 1.1) enum
:
enum
é apenas um índice de case
. Se a contagem de casos for de 2 a 256, éUInt8
.enum
bit 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
Eu escrevi uma extensão simples que fornece todas as enumerações em que o valor bruto é uma count
propriedade 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 count
propriedade para OptionSetType
onde não funcionará corretamente, então aqui está outra versão que requer conformidade explícita com o CaseCountable
protocolo 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.
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
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
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
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
0
e não tenham lacunas na sequência.
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
}
Uma versão do Swift 3 trabalhando com Int
enumeraçõ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. successor
nã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.count
para, UITableViews
como já mencionado por Zorayr.
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
}
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
}
Ou você pode simplesmente definir o _count
exterior 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 static
fizer isso)
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
.
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
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
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()
}
}
}
Isso é pequeno, mas acho que uma solução O (1) melhor seria a seguinte ( SOMENTE se sua enumeração estiver Int
começ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 Int
isso, recomendo esta solução.
guard
é contra COUNT
e gera um erro, retorna falso, etc. para resolver sua preocupação com a representação de tipos.