wake-up-neo.net

Wie bekomme ich die Zählung einer schnellen Enumeration?

Wie kann ich die Anzahl der Fälle in einer Swift-Enumeration ermitteln?

(Ich möchte vermeiden, dass alle Werte manuell aufzählt oder, wenn möglich, den alten " enum_count-Trick " verwendet.)

148
Robert Atkins

Ab Swift 4.2 (Xcode 10) können Sie Konformität mit dem Protokoll CaseIterable deklarieren. Dies funktioniert für alle Enumerationen ohne zugehörige Werte:

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

Die Anzahl der Fälle wird nun einfach mit ermittelt

print(Stuff.allCases.count) // 4

Weitere Informationen finden Sie unter

88
Martin R

Ich habe einen Blogbeitrag , auf den dies näher eingeht, aber solange der Rohtyp Ihres Enums eine Ganzzahl ist, können Sie einen Zähler auf diese Weise hinzufügen:

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

Xcode 10 Update

Übernehmen Sie das CaseIterable-Protokoll in das Enum. Es stellt eine statische allCases-Eigenschaft bereit, die alle Enum-Fälle als Collection enthält. Verwenden Sie einfach seine count-Eigenschaft, um zu wissen, wie viele Fälle die Aufzählung hat.

Siehe Martin's Antwort für ein Beispiel (und unterstütze seine Antworten eher als meine).


Warnung : Die unten beschriebene Methode scheint nicht mehr zu funktionieren.

Mir ist keine generische Methode bekannt, um die Anzahl der Aufzählungsfälle zu zählen. Ich habe jedoch festgestellt, dass die hashValue-Eigenschaft der Enum-Fälle inkrementell ist, beginnend mit Null und mit der Reihenfolge, die durch die Reihenfolge festgelegt wird, in der die Fälle deklariert werden. Der Hash der letzten Aufzählung plus eins entspricht der Anzahl der Fälle. 

Zum Beispiel mit dieser Enumeration:

enum Test {
    case ONE
    case TWO
    case THREE
    case FOUR

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

count gibt 4 zurück.

Ich kann nicht sagen, ob dies eine Regel ist oder ob sich dies in der Zukunft ändern wird, also auf eigenes Risiko :)

86
Antonio

Ich definiere ein wiederverwendbares Protokoll, das automatisch die Fallzählung basierend auf dem von Nate Cook veröffentlichten Ansatz durchführt.

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

Dann kann ich dieses Protokoll beispielsweise wie folgt wiederverwenden:

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

Erstellen Sie ein statisches AllValues-Array wie in dieser Antwort gezeigt.

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

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

...

let count = ProductCategory.allValues.count

Dies ist auch hilfreich, wenn Sie die Werte auflisten möchten und für alle Aufzählungstypen geeignet sind

35
david72

Wenn die Implementierung nichts gegen die Verwendung von Integer-Enummen hat, können Sie einen zusätzlichen Memberwert namens Count hinzufügen, um die Anzahl der Member in der Enumeration darzustellen - siehe Beispiel unten:

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

Jetzt können Sie die Anzahl der Mitglieder in der Aufzählung abrufen, indem Sie TableViewSections.Count.rawValue aufrufen, der für das obige Beispiel 2 zurückgibt. 

Wenn Sie die Enumeration in einer switch-Anweisung behandeln, stellen Sie sicher, dass Sie einen Assertionsfehler auslösen, wenn Sie auf das Member Count stoßen, an dem Sie es nicht erwarten:

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

Diese Art von Funktion kann die Zählung Ihrer Aufzählung zurückgeben. 

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

Oh, alle zusammen, Was ist mit Komponententests?

func testEnumCountIsEqualToNumberOfItemsInEnum() {

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

    XCTAssert(max == Test.count)
}

Dies kombiniert mit der Lösung von Antonio:

enum Test {

    case one
    case two
    case three
    case four

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

im Hauptcode erhalten Sie O(1) plus Sie erhalten einen fehlgeschlagenen Test, wenn ein Benutzer einen Listenfall five hinzufügt und die Implementierung von count nicht aktualisiert .

9
buildsucceeded

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

anzahl: eEventTabType.allValues.count

index: objeEventTabType.index

Genießen :)

9
kalpesh jetani

Diese Funktion basiert auf 2 undokumentierten current (Swift 1.1) enum-Verhalten:

  • Das Speicherlayout von enum ist nur ein Index von case. Wenn die Anzahl der Fälle zwischen 2 und 256 liegt, ist dies UInt8.
  • Wenn die enum aus invalid case index bit-casted wurde, lautet ihre hashValue0

Also auf eigenes Risiko verwenden :)

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

Verwendungszweck:

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

Ich habe eine einfache Erweiterung geschrieben, die allen Enummen, in denen der Rohwert eine Ganzzahl ist, eine count-Eigenschaft gibt:

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

Leider gibt es die count-Eigenschaft an OptionSetType, wo sie nicht ordnungsgemäß funktioniert. Daher gibt es eine weitere Version, die die explizite Konformität mit dem CaseCountable-Protokoll für alle Listen erfordert, deren Fälle Sie zählen möchten:

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 ist dem Ansatz von Tom Pelaia sehr ähnlich, funktioniert aber mit allen Integer-Typen.

5
bzz

Natürlich ist es nicht dynamisch, aber für viele Zwecke können Sie mit einer statischen var, die zu Ihrem Enum hinzugefügt wird, auskommen

static var count: Int{ return 7 }

und dann als EnumName.count verwenden

4
Ian Dundas

Warum machst du das alles so komplex? Der SIMPLEST-Zähler von Int enum fügt hinzu:

case Count

Schlussendlich. Und ... Viola - jetzt haben Sie die Zählung - schnell und einfach

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

* Auf Swift 4.2 (Xcode 10) können Sie Folgendes verwenden:

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

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

Für meinen Anwendungsfall in einer Codebasis, in der mehrere Personen Schlüssel zu einer Aufzählung hinzufügen könnten, und diese Fälle sollten alle in der Eigenschaft allKeys verfügbar sein, ist es wichtig, dass alle Schlüssel gegen die Schlüssel in der Aufzählung überprüft werden. Hiermit soll vermieden werden, dass jemand vergisst, den Schlüssel zur Liste aller Schlüssel hinzuzufügen. Durch das Vergleichen der Anzahl des allKeys-Arrays (das zuerst als Gruppe erstellt wurde, um Dupes zu vermeiden) mit der Anzahl der Schlüssel in der Aufzählung wird sichergestellt, dass alle vorhanden sind.

Einige der obigen Antworten zeigen den Weg, um dies in Swift 2 zu erreichen, aber in Swift 3 funktioniert keine. Hier ist die Swift 3 formatierte Version:

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

Abhängig von Ihrem Anwendungsfall möchten Sie möglicherweise den Test in der Entwicklung ausführen, um den Aufwand für die Verwendung aller Schlüssel für jede Anforderung zu vermeiden

2

Wenn Sie Ihren Code nicht in der letzten Enumeration verwenden möchten, können Sie diese Funktion in Ihrer Enumeration erstellen.

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, die mit Int Typ enum arbeitet:

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

Credits: Basierend auf den Antworten von bzz und Nate Cook.

Das generische IntegerType (in Swift 3 in Integer umbenannt) wird nicht unterstützt, da es sich um einen stark fragmentierten generischen Typ handelt, dem viele Funktionen fehlen. successor ist mit Swift 3 nicht mehr verfügbar.

Beachten Sie, dass der Kommentar von Code Commander zu Nate Cooks Antwort noch gültig ist:

Während Nice ist, weil Sie einen Wert nicht hartcodieren müssen, wird dies instanziiert jeden Aufzählungswert bei jedem Aufruf. Das ist O (n) anstelle von O (1).

Soweit ich weiß, gibt es derzeit keine Problemumgehung, wenn diese Funktion als Protokollerweiterung verwendet wird (und nicht wie in Nate Cook in jedes Enum implementiert wird), da statisch gespeicherte Eigenschaften in generischen Typen nicht unterstützt werden.

Für kleine Aufzählungen sollte dies jedoch kein Problem sein. Ein typischer Anwendungsfall wäre der von Zorayr bereits erwähnte section.count für UITableViews.

Die Antwort von Matthieu Riegler erweitert, ist dies eine Lösung für Swift 3 , für die keine Generics erforderlich sind und die mit dem Enummentyp mit EnumType.elementsCount aufgerufen werden kann:

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

Ich habe dieses Problem für mich gelöst, indem ich ein Protokoll (EnumIntArray) und eine globale Utility-Funktion (enumIntArray) erstellt habe, mit denen es sehr einfach ist, einer Enumeration (mit Swift 1.2) eine "All" -Variable hinzuzufügen. Die "all" -Variable enthält ein Array aller Elemente in der Enumeration, sodass Sie all.count für die Zählung verwenden können

Es funktioniert nur mit Enumerationen, die Rohwerte vom Typ Int verwenden, aber es kann für andere Typen eine Anregung geben.

Sie befasst sich auch mit den "Lücken bei der Nummerierung" und den "übermäßigen Zeitaufwand für Iterationen", die ich oben und anderswo gelesen habe.

Die Idee ist, das EnumIntArray-Protokoll zu Ihrem Enum hinzuzufügen und dann eine statische "All" -Variable zu definieren, indem Sie die enumIntArray-Funktion aufrufen, und das erste Element (und das letzte Element, falls die Nummerierung Lücken aufweist) angeben.

Da die statische Variable nur einmal initialisiert wird, trifft der Aufwand für alle rohen Werte nur ein Mal auf Ihr Programm.

beispiel (ohne Lücken):

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

beispiel (mit Lücken):

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

Hier ist der Code, der es implementiert:

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

Die folgende Methode stammt aus CoreKit und ähnelt den Antworten, die andere vorgeschlagen haben. Dies funktioniert mit 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
}

Dann müssen Sie einfach Weekdays.allValues.count aufrufen.

0
ThomasW

Oder Sie können den _count außerhalb der Enumeration definieren und statisch anhängen:

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
}

Unabhängig davon, wie viele Aufzählungen Sie erstellen, wird sie nur einmal erstellt.

(Löschen Sie diese Antwort, wenn static das tut.)

0
A T