Comment puis-je déterminer le nombre d'observations dans une énumération Swift?
(Je voudrais éviter d' énumérer manuellement toutes les valeurs , ou d'utiliser le vieux « truc enum_count » si possible.)
Comment puis-je déterminer le nombre d'observations dans une énumération Swift?
(Je voudrais éviter d' énumérer manuellement toutes les valeurs , ou d'utiliser le vieux « truc enum_count » si possible.)
Réponses:
Depuis Swift 4.2 (Xcode 10), vous pouvez déclarer la conformité au CaseIterable
protocole, 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 obtenu simplement avec
print(Stuff.allCases.count) // 4
Pour plus d'informations, consultez
J'ai un article de blog qui va plus en détail à ce sujet, mais tant que le type brut de votre énumération est un entier, vous pouvez ajouter un dé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
}()
}
case A=1, B=3
?
enum
avoir une Int
valeur brute que vous avez oublié de mentionner: les énumérations Swift avec des valeurs brutes Int n'ont pas à commencer à partir de 0 (même si c'est le comportement par défaut) et leurs valeurs brutes peuvent être arbitraires, elles n'ont pas pour incrémenter de 1 (même si c'est le comportement par défaut).
Mise à jour Xcode 10
Adoptez le CaseIterable
protocole dans l'énumération, il fournit une allCases
propriété statique qui contient tous les cas d'énumération sous forme de fichier Collection
. Utilisez simplement sa count
propriété pour savoir combien de cas l'énumération a.
Voir la réponse de Martin pour un exemple (et voter pour ses réponses plutôt que les miennes)
avertissement : la méthode ci-dessous ne semble plus fonctionner.
Je ne connais aucune méthode générique pour compter le nombre de cas d'énumération. J'ai cependant remarqué que la hashValue
propriété des cas enum est incrémentale, à partir de zéro, et avec l'ordre déterminé par l'ordre dans lequel les cas sont déclarés. Ainsi, le hachage de la dernière énumération 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 utilisez à vos risques et périls :)
hashValues
ces choses; tout ce que nous savons, c'est qu'il s'agit d'une valeur unique aléatoire - pourrait changer très facilement à l'avenir en fonction de certains détails d'implémentation du compilateur; mais dans l'ensemble, le manque de fonctionnalité de comptage intégrée est inquiétant.
case ONE = 0
, vous pouvez alors remplacer hashValue
par rawValue
.
static var count = 4
plutôt que de laisser votre destin dans le sort des futures implémentations de Swift
Je définis un protocole réutilisable qui effectue automatiquement le décompte des 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)
count++
de count+=1
car la ++
notation sera supprimée dans Swift 3
static var caseCount: Int { get }
? pourquoi le besoin de l ' static func
?
case A=1, B=3
?
0
et ne présentent aucune lacune.
Créer un tableau statique allValues comme indiqué dans cette réponse
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
static let count = allValues.count
. Ensuite, vous pouvez rendre le allValues
privé si vous le souhaitez.
Si l'implémentation n'a rien contre l'utilisation d' Count
énumérations entières, vous pouvez ajouter une valeur de membre supplémentaire appelée pour représenter le nombre de membres dans l'énumération - 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ération en appelant, TableViewSections.Count.rawValue
ce qui renverra 2 pour l'exemple ci-dessus.
Lorsque vous gérez l'énumération dans une instruction switch, assurez-vous de lancer un échec d'assertion lorsque vous rencontrez le Count
membre là 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!")
}
}
Ce type de fonction est capable de renvoyer le décompte de votre énumération.
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
est également Hashable
du même type.
Énumération de chaîne 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)!
}
}
compter : eEventTabType.allValues.count
index: objeEventTabType.index
Prendre plaisir :)
Oh hé 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 cas d'énumération five
et ne met pas à jour l'implémentation de count
.
Cette fonction repose sur 2 comportements courants non documentés (Swift 1.1) enum
:
enum
n'est qu'un index de case
. Si le nombre de cas est compris entre 2 et 256, c'estUInt8
.enum
bit a été converti à partir d'un index de cas non valide , il hashValue
est0
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
J'ai écrit une extension simple qui donne à toutes les énumérations où la valeur brute est un entier une count
propriété:
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 donne la count
propriété OptionSetType
là où cela ne fonctionnera pas correctement, voici donc une autre version qui nécessite une conformité explicite au CaseCountable
protocole pour toute énumération des cas 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 fonctionne avec tous les types entiers.
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
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
* Sur Swift 4.2 (Xcode 10) peut utiliser:
enum EnumNameType: CaseIterable {
case first
case second
case third
}
print(EnumNameType.allCases.count) //3
Pour mon cas d'utilisation, dans une base de code où plusieurs personnes pourraient ajouter des clés à une énumération, et ces cas devraient tous être disponibles dans la propriété allKeys, il est important que allKeys soit validé par rapport aux clés de l'énumération. Ceci afin d'éviter que quelqu'un oublie d'ajouter sa clé à la liste de toutes les clés.Faire correspondre le nombre du tableau allKeys (d'abord créé comme un ensemble pour éviter les dupes) avec le nombre de clés dans l'énumération garantit qu'elles sont toutes présentes.
Certaines des réponses ci-dessus montrent la manière d'y parvenir 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 voudrez peut-être simplement exécuter le test en développement pour éviter la surcharge liée à l'utilisation de allKeys à chaque demande
Pourquoi rendez-vous tout cela si complexe? Le compteur SIMPLEST de l'énumération Int consiste à ajouter:
case Count
À la fin. Et ... alto - maintenant vous avez le décompte - rapide et simple
0
et qu'il n'y ait pas de lacunes dans la séquence.
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
}
Une version Swift 3 fonctionnant avec Int
les énumérations de types:
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.
Generic IntegerType
(dans Swift 3 renommé en Integer
) n'est pas pris en charge, car il s'agit d'un type générique fortement 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 cela soit agréable car vous n'avez pas besoin de coder en dur une valeur, cela instanciera chaque valeur enum à chaque fois qu'elle est appelée. C'est O (n) au lieu de O (1).
Pour autant que je sache, il n'y a actuellement aucune solution de contournement lors de l'utilisation de cette extension de protocole (et de la non-implémentation dans chaque énumération comme Nate Cook l'a fait) en raison des propriétés stockées statiques qui ne sont pas prises en charge dans les types génériques.
Quoi qu'il en soit, pour les petites énumérations, cela ne devrait pas poser de problème. Un cas d'utilisation typique serait le section.count
pour UITableViews
comme déjà mentionné par Zorayr.
En étendant 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 peut être facilement 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
}
J'ai résolu ce problème pour moi-même en créant un protocole (EnumIntArray) et une fonction utilitaire globale (enumIntArray) qui facilitent l'ajout d'une variable "All" à n'importe quelle énumération (en utilisant 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 ne fonctionne qu'avec les énumérations qui utilisent des valeurs brutes de type Int, mais peut-être peut-il fournir une certaine inspiration pour d'autres types.
Il aborde également les problèmes de «manque de numérotation» et de «temps excessif pour itérer» 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 «all» en appelant la fonction enumIntArray et de lui fournir le premier élément (et le dernier s'il y a des lacunes dans la numérotation).
Étant donné que la variable statique n'est initialisée qu'une seule fois, la surcharge liée au passage de toutes les valeurs brutes ne touche 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
}
Ou vous pouvez simplement définir l' _count
extérieur de l'énumération et l'attacher de manière statique:
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 cette façon, quel que soit le nombre d'énumérations que vous créez, il ne sera créé qu'une seule fois.
(supprimez cette réponse si static
c'est le cas)
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
.
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
mais soyez prudent avec l'utilisation sur les types non-enum. Une solution de contournement pourrait être:
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
Il peut utiliser une constante statique qui contient la dernière valeur de l'énumération plus un.
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()
}
}
}
C'est mineur, mais je pense qu'une meilleure solution O (1) serait la suivante ( UNIQUEMENT si votre énumération Int
commence à 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 réponse actuellement sélectionnée, je crois toujours, est la meilleure réponse pour toutes les énumérations, sauf si vous travaillez avec, Int
je recommande cette solution.
guard
contre laquelle s COUNT
et renvoie une erreur, renvoie false, etc. pour résoudre votre problème de représentation des types.