web-dev-qa-db-fra.com

Comment puis-je obtenir le compte d'un enum Swift?

Comment puis-je déterminer le nombre de cas dans une énumération rapide?

(Je voudrais éviter énumérer manuellement toutes les valeurs , ou utiliser l'ancien " enum_count trick " si possible)

148
Robert Atkins

À partir de Swift 4.2 (Xcode 10), vous pouvez déclarer la conformité Au protocole CaseIterable, cela fonctionne pour toutes les énumérations Sans valeurs associées:

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

Le nombre de cas est maintenant simplement obtenu avec

print(Stuff.allCases.count) // 4

Pour plus d'informations, voir

88
Martin R

J'ai un article de blog qui donne plus de détails à ce sujet, mais tant que le type brut de votre enum est un entier, vous pouvez ajouter un compte de cette façon:

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
    }()
}
137
Nate Cook

Mise à jour Xcode 10

Adoptez le protocole CaseIterable dans l’énumération, elle fournit une propriété statique allCases qui contient tous les cas d’énumération en tant que Collection. Il suffit d’utiliser sa propriété count pour savoir combien de cas l’énumération a.

Voir la réponse de Martin pour un exemple (et augmenter ses réponses plutôt que les miennes)


Attention: la méthode ci-dessous ne semble plus fonctionner.

Je ne connais aucune méthode générique pour compter le nombre de cas enum. J'ai toutefois remarqué que la propriété hashValue des observations enum est incrémentielle, à partir de zéro et dans l'ordre déterminé par l'ordre dans lequel les observations sont déclarées. Ainsi, le hash du dernier enum plus un correspond au nombre de cas. 

Par exemple avec cette énumération:

enum Test {
    case ONE
    case TWO
    case THREE
    case FOUR

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

count renvoie 4.

Je ne peux pas dire si c'est une règle ou si cela changera un jour, alors utiliser à vos risques et périls

86
Antonio

Je définis un protocole réutilisable qui effectue automatiquement le nombre de cas en fonction de l'approche publiée par 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
    }
}

Ensuite, je peux réutiliser ce protocole par exemple comme suit:

enum Planet : Int, CaseCountable {
    case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}
//..
print(Planet.caseCount)
69
Tom Pelaia

Créez un tableau statique allValues ​​comme indiqué dans ce answer

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

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

...

let count = ProductCategory.allValues.count

Ceci est également utile lorsque vous souhaitez énumérer les valeurs et fonctionne pour tous les types Enum.

35
david72

Si l'implémentation n'a rien contre l'utilisation d'énumérations entières, vous pouvez ajouter une valeur de membre supplémentaire appelée Count pour représenter le nombre de membres de l'énum, ​​voir l'exemple ci-dessous:

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

Vous pouvez maintenant obtenir le nombre de membres dans l'énum en appelant TableViewSections.Count.rawValue qui retournera 2 pour l'exemple ci-dessus. 

Lorsque vous manipulez l'énumération dans une instruction switch, assurez-vous de générer un échec d'assertion lorsque vous rencontrez le membre Count où vous ne vous attendez pas:

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!")
  }
}
15
Zorayr

Ce type de fonction est capable de retourner le compte de votre 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
   }
14
Matthieu Riegler

Oh hey tout le monde, qu'en est-il des tests unitaires?

func testEnumCountIsEqualToNumberOfItemsInEnum() {

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

    XCTAssert(max == Test.count)
}

Ceci combiné avec la solution d'Antonio:

enum Test {

    case one
    case two
    case three
    case four

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

dans le code principal vous donne O(1) plus vous obtenez un test qui échoue si quelqu'un ajoute un enum case five et ne met pas à jour l'implémentation de count.

9
buildsucceeded

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

compte: eEventTabType.allValues.count

index: objeEventTabType.index

Prendre plaisir :)

9
kalpesh jetani

Cette fonction repose sur 2 comportements non documentés current (Swift 1.1) enum:

  • La disposition de la mémoire de enum est simplement un index de case. Si le nombre de cas est compris entre 2 et 256, il s'agit de UInt8.
  • Si la enum a été convertie en bits à partir de l'index de cas invalid, sa hashValue est 0

Alors utilisez à vos risques et périls :)

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

Usage:

enum Foo:String {
    case C000 = "foo"
    case C001 = "bar"
    case C002 = "baz"
}
enumCaseCount(Foo) // -> 3
7
rintaro

J'ai écrit une extension simple qui donne à tous les enums où la valeur brute est un entier une propriété count:

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

Malheureusement, il attribue la propriété count à OptionSetType s'il ne fonctionne pas correctement. Voici donc une autre version nécessitant la conformité explicite au protocole CaseCountable pour toute énumération que vous souhaitez compter:

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

C'est très similaire à l'approche publiée par Tom Pelaia, mais ça marche avec tous les types d'entiers.

5
bzz

Bien sûr, ce n’est pas dynamique, mais pour de nombreuses utilisations, vous pouvez vous en tirer avec une variable statique ajoutée à votre Enum

static var count: Int{ return 7 }

puis utilisez-le comme EnumName.count

4
Ian Dundas

Pourquoi faites-vous tout cela si complexe? Le compteur SIMPLEST d'Intern est d'ajouter:

case Count

À la fin. Et ... alto - maintenant vous avez le compte - simple et rapide

2
Dimitar Marinov
enum EnumNameType: Int {
    case first
    case second
    case third

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

print(EnumNameType.count) //3

OR

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

print(EnumNameType.count.rawValue) //3

* Sur Swift 4.2 (Xcode 10), vous pouvez utiliser:

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

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

Pour mon cas d'utilisation, dans une base de code où plusieurs personnes pourraient ajouter des clés à une énumération, et que ces cas devraient tous être disponibles dans la propriété allKeys, il est important que toutes les clés soient validées par rapport aux clés de l'enum. C'est pour éviter à quelqu'un d'oublier d'ajouter sa clé à la liste de toutes les clés. Faire correspondre le nombre du tableau allKeys (créé initialement en tant qu'ensemble pour éviter les dupes) au nombre de clés de l'énumération garantit qu'elles sont toutes présentes.

Certaines des réponses ci-dessus montrent la voie à suivre dans Swift 2, mais aucune ne fonctionne dans Swift 3 . Voici la version formatée 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)
}

En fonction de votre cas d'utilisation, vous pouvez simplement exécuter le test en développement pour éviter la surcharge liée à l'utilisation de allKeys sur chaque demande.

2
Chris Mitchelmore

Si vous ne voulez pas baser votre code dans la dernière énumération, vous pouvez créer cette fonction dans votre énumération.

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
Gabriel Araujo

A Swift 3 version fonctionnant avec des énumérations de type Int:

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édits: Basé sur les réponses de Bzz et Nate Cook.

Le nom générique IntegerType (renommé Integer dans Swift 3) n'est pas pris en charge, car il s'agit d'un type générique très fragmenté qui manque de nombreuses fonctions. successor n'est plus disponible avec Swift 3.

Sachez que le commentaire de Code Commander à la réponse de Nate Cooks est toujours valide:

Bien que Nice, parce que vous n’avez pas besoin de coder en dur une valeur, ce sera instancier chaque valeur enum à chaque appel. C'est O (n) au lieu de O (1).

Autant que je sache, il n’existe actuellement aucune solution de contournement lorsqu’on utilise cela comme extension de protocole (et non dans chaque énumération comme le faisait Nate Cook), car les propriétés stockées statiques ne sont pas prises en charge dans les types génériques.

Quoi qu'il en soit, cela ne devrait pas poser de problème pour les petites circonscriptions. Un cas d'utilisation typique serait le section.count pour UITableViews déjà mentionné par Zorayr.

En prolongeant la réponse de Matthieu Riegler, il s’agit d’une solution pour Swift 3 qui ne nécessite pas l’utilisation de génériques, et qui peut facilement être appelée en utilisant le type enum avec 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
}
1
matsoftware

J'ai moi-même résolu ce problème en créant un protocole (EnumIntArray) et une fonction d'utilitaire global (enumIntArray) qui facilitent l'ajout d'une variable "All" à n'importe quel enum (à l'aide de Swift 1.2). La variable "all" contiendra un tableau de tous les éléments de l'énumération afin que vous puissiez utiliser all.count pour le nombre

Cela fonctionne uniquement avec les enums qui utilisent des valeurs brutes de type Int, mais peut-être peut-il inspirer d'autres types.

Il aborde également les problèmes de "manque de numérotation" et de "temps excessif pour parcourir" que j'ai lus ci-dessus et ailleurs.

L'idée est d'ajouter le protocole EnumIntArray à votre enum, puis de définir une variable statique "toutes" en appelant la fonction enumIntArray et de lui fournir le premier élément (et le dernier en cas de lacunes dans la numérotation).

Étant donné que la variable statique n’est initialisée qu’une seule fois, la surcharge de toutes les valeurs brutes n’atteint votre programme qu’une seule fois.

exemple (sans lacunes):

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

exemple (avec des lacunes):

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

Voici le code qui l'implémente:

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
Alain T.
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)")

0
Nupur Sharma

La méthode suivante provient de CoreKit et est similaire aux réponses suggérées par d’autres. Cela fonctionne avec 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
}

Ensuite, il vous suffit d'appeler Weekdays.allValues.count.

0
ThomasW

Ou vous pouvez simplement définir le _count en dehors de l'énum, ​​et l'attacher statiquement:

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
}

Ainsi, quel que soit le nombre d’énumérations que vous créez, il ne sera créé qu’une fois.

(supprimez cette réponse si static le fait)

0
A T