Wie kann ich die Anzahl der Fälle in einer Swift-Aufzählung bestimmen?
(Ich möchte vermeiden, alle Werte manuell aufzulisten oder wenn möglich den alten " enum_count-Trick " zu verwenden.)
Wie kann ich die Anzahl der Fälle in einer Swift-Aufzählung bestimmen?
(Ich möchte vermeiden, alle Werte manuell aufzulisten oder wenn möglich den alten " enum_count-Trick " zu verwenden.)
Antworten:
Ab Swift 4.2 (Xcode 10) können Sie die Konformität mit dem CaseIterable
Protokoll deklarieren. Dies funktioniert für alle Aufzählungen 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
Ich habe einen Blog-Beitrag , der ausführlicher darauf eingeht, aber solange der Rohtyp Ihrer Aufzählung eine Ganzzahl ist, können Sie auf diese Weise eine Zählung 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
}()
}
case A=1, B=3
?
enum
einen mit Int
Rohwert , dass Sie vergessen zu erwähnen: Swift Enum ist mit Int Rohwerte nicht über 0 starten (auch wenn das das Standardverhalten) und ihre Ausgangswerte willkürlich können, haben sie nicht um 1 erhöhen (obwohl dies das Standardverhalten ist).
Xcode 10 Update
Übernehmen Sie das CaseIterable
Protokoll in die Aufzählung, es bietet eine statische allCases
Eigenschaft, die alle Aufzählungsfälle als enthält Collection
. Verwenden Sie einfach seine count
Eigenschaft, um zu wissen, wie viele Fälle die Aufzählung hat.
Ein Beispiel finden Sie in Martins Antwort (und stimmen Sie eher seinen als meinen Antworten zu)
Warnung : Die folgende Methode scheint nicht mehr zu funktionieren.
Mir ist keine generische Methode bekannt, um die Anzahl der Enum-Fälle zu zählen. Ich habe jedoch festgestellt, dass die hashValue
Eigenschaft der Enum-Fälle inkrementell ist, beginnend bei Null und mit der Reihenfolge, die durch die Reihenfolge bestimmt wird, in der die Fälle deklariert werden. Der Hash der letzten Aufzählung plus eins entspricht also der Anzahl der Fälle.
Zum Beispiel mit dieser Aufzählung:
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 sie sich in Zukunft jemals ändern wird. Verwenden Sie sie daher auf eigenes Risiko :)
hashValues
bei diesen Dingen nicht wirklich darauf verlassen . Wir wissen nur, dass es sich um einen zufälligen eindeutigen Wert handelt, der sich in Zukunft sehr leicht ändern kann, abhängig von einigen Details der Compiler-Implementierung. Insgesamt ist der Mangel an eingebauter Zählfunktion jedoch störend.
case ONE = 0
, können Sie ersetzen Sie dann hashValue
mit rawValue
.
static var count = 4
als Ihr Schicksal im Schicksal zukünftiger Implementierungen von Swift
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)
count++
, count+=1
da die ++
Notation in Swift 3
static var caseCount: Int { get }
? warum die Notwendigkeit für die static func
?
case A=1, B=3
?
0
und keine Lücken aufweisen.
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 funktioniert für alle Aufzählungstypen
static let count = allValues.count
. Dann können Sie das allValues
privat machen, wenn Sie gewünscht werden.
Wenn die Implementierung nichts gegen die Verwendung ganzzahliger Aufzählungen hat, können Sie einen zusätzlichen Elementwert hinzufügen, der aufgerufen wird Count
, um die Anzahl der Elemente in der Aufzählung darzustellen - siehe Beispiel unten:
enum TableViewSections : Int {
case Watchlist
case AddButton
case Count
}
Jetzt können Sie die Anzahl der Mitglieder in der Enumeration durch Aufrufen abrufen TableViewSections.Count.rawValue
. Im obigen Beispiel wird 2 zurückgegeben.
Wenn Sie die Aufzählung in einer switch-Anweisung behandeln, stellen Sie sicher, dass ein Assertionsfehler ausgelöst wird, wenn Sie auf das Count
Mitglied stoßen, bei 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!")
}
}
Diese Art von Funktion kann die Anzahl 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
}
enum
ist auch Hashable
vom gleichen Typ.
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 :)
Oh hey alle zusammen, was ist mit Unit-Tests?
func testEnumCountIsEqualToNumberOfItemsInEnum() {
var max: Int = 0
while let _ = Test(rawValue: max) { max += 1 }
XCTAssert(max == Test.count)
}
Dies kombiniert mit Antonios Lösung:
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) und Sie erhalten einen fehlgeschlagenen Test, wenn jemand einen Enum-Fall hinzufügt five
und die Implementierung von nicht aktualisiert count
.
Diese Funktion basiert auf 2 undokumentierten aktuellen (Swift 1.1) enum
Verhalten:
enum
ist nur ein Index von case
. Wenn die Anzahl der Fälle zwischen 2 und 256 liegt, ist dies der Fall UInt8
.enum
aus einem ungültigen Fallindex bitweise umgewandelt wurde , hashValue
ist es0
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")
}
}
Verwendung:
enum Foo:String {
case C000 = "foo"
case C001 = "bar"
case C002 = "baz"
}
enumCaseCount(Foo) // -> 3
Ich habe eine einfache Erweiterung geschrieben, die allen Aufzählungen, bei 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, OptionSetType
wo es nicht richtig funktioniert, daher ist hier eine andere Version, die eine explizite Konformität mit dem CaseCountable
Protokoll für jede Aufzählung erfordert, welche 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 von Tom Pelaia veröffentlichten Ansatz sehr ähnlich, funktioniert jedoch mit allen ganzzahligen Typen.
Natürlich ist es nicht dynamisch, aber für viele Zwecke können Sie mit einer statischen Variable auskommen, die Ihrer Aufzählung hinzugefügt wurde
static var count: Int{ return 7 }
und dann verwenden als EnumName.count
enum EnumNameType: Int {
case first
case second
case third
static var count: Int { return EnumNameType.third.rawValue + 1 }
}
print(EnumNameType.count) //3
ODER
enum EnumNameType: Int {
case first
case second
case third
case count
}
print(EnumNameType.count.rawValue) //3
* Auf Swift 4.2 (Xcode 10) kann verwendet werden:
enum EnumNameType: CaseIterable {
case first
case second
case third
}
print(EnumNameType.allCases.count) //3
Für meinen Anwendungsfall ist es in einer Codebasis, in der mehrere Personen einer Aufzählung Schlüssel hinzufügen können und diese Fälle alle in der Eigenschaft allKeys verfügbar sein sollten, wichtig, dass allKeys anhand der Schlüssel in der Aufzählung überprüft wird. Dies soll verhindern, dass jemand vergisst, seinen Schlüssel zur Liste aller Schlüssel hinzuzufügen.Durch Abgleichen der Anzahl des allKeys-Arrays (das zuerst als Satz 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, wie dies in Swift 2 erreicht werden kann, 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 den Test möglicherweise nur in der Entwicklung ausführen, um den Aufwand für die Verwendung von allKeys bei jeder Anforderung zu vermeiden
Warum machst du das alles so komplex? Der EINFACHSTE Zähler von Int enum ist das Hinzufügen von:
case Count
Schlussendlich. Und ... Bratsche - jetzt haben Sie die Zählung - schnell und einfach
0
und dürfen keine Lücken in der Sequenz aufweisen.
Wenn Sie Ihren Code nicht auf der letzten Aufzählung basieren möchten, können Sie diese Funktion in Ihrer Aufzählung 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
}
Eine Swift 3- Version, die mit Int
Typaufzählungen 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.
Generic IntegerType
(in Swift 3 umbenannt in Integer
) 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 zur Antwort von Nate Cooks weiterhin gültig ist:
Dies ist zwar hilfreich, da Sie keinen Wert fest codieren müssen, es instanziiert jedoch bei jedem Aufruf jeden Aufzählungswert. Das ist O (n) anstelle von O (1).
Soweit ich weiß, gibt es derzeit keine Problemumgehung, wenn Sie diese als Protokollerweiterung verwenden (und nicht wie Nate Cook in jeder Aufzählung implementieren), da statische gespeicherte Eigenschaften in generischen Typen nicht unterstützt werden.
Auf jeden Fall sollte dies für kleine Aufzählungen kein Problem sein. Ein typischer Anwendungsfall wäre die section.count
für UITableViews
wie bereits von Zorayr erwähnt.
Erweiterung der Antwort von Matthieu Riegler: Dies ist eine Lösung für Swift 3 , für die keine Generika erforderlich sind. Sie kann einfach mit dem Aufzählungstyp aufgerufen werden mit 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
}
Ich habe dieses Problem für mich selbst gelöst, indem ich ein Protokoll (EnumIntArray) und eine globale Dienstprogrammfunktion (enumIntArray) erstellt habe, mit denen es sehr einfach ist, einer Aufzählung eine "All" -Variable hinzuzufügen (mit swift 1.2). Die Variable "all" enthält ein Array aller Elemente in der Aufzählung, sodass Sie all.count für die Zählung verwenden können
Es funktioniert nur mit Aufzählungen, die Rohwerte vom Typ Int verwenden, kann aber möglicherweise Inspiration für andere Typen liefern.
Es befasst sich auch mit den Problemen "Nummerierung" und "übermäßiger Zeit zum Iterieren", die ich oben und anderswo gelesen habe.
Die Idee ist, das EnumIntArray-Protokoll zu Ihrer Aufzählung hinzuzufügen und dann eine statische Variable "all" zu definieren, indem Sie die Funktion enumIntArray aufrufen und das erste Element (und das letzte, wenn es Lücken in der Nummerierung gibt) bereitstellen.
Da die statische Variable nur einmal initialisiert wird, trifft der Aufwand für das Durchlaufen aller Rohwerte Ihr Programm nur einmal.
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
}
Oder Sie können einfach die _count
Außenseite der Aufzählung 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
}
Auf diese Weise wird die Anzahl der von Ihnen erstellten Aufzählungen immer nur einmal erstellt.
(Löschen Sie diese Antwort, wenn dies der static
Fall ist.)
Die folgende Methode stammt von CoreKit und ähnelt den Antworten, die einige 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 nur noch anrufen 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
Seien Sie jedoch vorsichtig bei der Verwendung mit Nicht-Enum-Typen. Einige Problemumgehungen könnten sein:
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
Es kann eine statische Konstante verwendet werden, die den letzten Wert der Aufzählung plus eins enthält.
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()
}
}
}
Dies ist geringfügig, aber ich denke, eine bessere O (1) -Lösung wäre die folgende ( NUR, wenn Ihre Aufzählung Int
bei x usw. beginnt):
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
}
Die aktuell ausgewählte Antwort ist meines Erachtens immer noch die beste Antwort für alle Aufzählungen. Wenn Sie nicht mit Int
dieser Antwort arbeiten, empfehle ich diese Lösung.
guard
gegen den COUNT
ein Fehler ausgegeben wird, der false zurückgibt usw., um Ihr Problem der Darstellung von Typen zu lösen.