Jak mogę określić liczbę przypadków w wyliczeniu Swift?
(Chciałbym uniknąć ręcznego wyliczania wszystkich wartości lub używania starej „ sztuczki enum_count ”, jeśli to możliwe).
Jak mogę określić liczbę przypadków w wyliczeniu Swift?
(Chciałbym uniknąć ręcznego wyliczania wszystkich wartości lub używania starej „ sztuczki enum_count ”, jeśli to możliwe).
Odpowiedzi:
Począwszy od Swift 4.2 (Xcode 10) możesz zadeklarować zgodność z CaseIterable
protokołem, działa to dla wszystkich wyliczeń bez powiązanych wartości:
enum Stuff: CaseIterable {
case first
case second
case third
case forth
}
Liczba przypadków jest teraz po prostu uzyskiwana za pomocą
print(Stuff.allCases.count) // 4
Aby uzyskać więcej informacji, zobacz
Mam wpis na blogu, który zawiera bardziej szczegółowe informacje na ten temat, ale dopóki surowy typ wyliczenia jest liczbą całkowitą, możesz dodać liczbę w ten sposób:
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
posiadania Int
surowej wartości, o której zapomniałeś wspomnieć: Szybkie wyliczanie z Int surowymi wartościami nie muszą zaczynać się od 0 (chociaż jest to zachowanie domyślne), a ich surowe wartości mogą być dowolne, nie mają zwiększać o 1 (nawet jeśli jest to zachowanie domyślne).
Aktualizacja Xcode 10
Przyjęcie CaseIterable
protokołu w wyliczeniu, zapewnia on allCases
właściwość statyczną, która zawiera wszystkie przypadki wyliczania jako Collection
. Wystarczy użyć jego count
właściwości, aby dowiedzieć się, ile przypadków ma wyliczenie.
Zobacz przykład Martina (i głosuj raczej na jego odpowiedzi niż moje)
Ostrzeżenie : poniższa metoda wydaje się już nie działać.
Nie znam żadnej ogólnej metody liczenia liczby przypadków wyliczania. Zauważyłem jednak, że hashValue
właściwość przypadków wyliczeniowych jest przyrostowa, zaczynając od zera, i według kolejności określonej przez porządek, w którym przypadki są zadeklarowane. Tak więc skrót ostatniego wyliczenia plus jeden odpowiada liczbie przypadków.
Na przykład z tym wyliczeniem:
enum Test {
case ONE
case TWO
case THREE
case FOUR
static var count: Int { return Test.FOUR.hashValue + 1}
}
count
zwraca 4.
Nie mogę powiedzieć, czy to reguła, czy zmieni się ona w przyszłości, więc używaj na własne ryzyko :)
hashValues
tych rzeczach; wiemy tylko, że jest to jakaś losowa unikalna wartość - może w przyszłości bardzo łatwo ulec zmianie w zależności od szczegółów implementacyjnych kompilatora; ale ogólnie niepokojący jest brak wbudowanej funkcji liczenia.
case ONE = 0
, można następnie wymienić hashValue
z rawValue
.
static var count = 4
niż pozostawić swoje przeznaczenie w losie przyszłych wdrożeń Swift
Definiuję protokół wielokrotnego użytku, który automatycznie wykonuje licznik spraw na podstawie podejścia opublikowanego przez 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
}
}
Następnie mogę ponownie użyć tego protokołu, na przykład w następujący sposób:
enum Planet : Int, CaseCountable {
case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}
//..
print(Planet.caseCount)
count++
na, count+=1
ponieważ ++
notacja zostanie usunięta w Swift 3
static var caseCount: Int { get }
? skąd ta potrzeba static func
?
case A=1, B=3
?
0
i nie miały żadnych luk.
Utwórz statyczną tablicę allValues, jak pokazano w tej odpowiedzi
enum ProductCategory : String {
case Washers = "washers", Dryers = "dryers", Toasters = "toasters"
static let allValues = [Washers, Dryers, Toasters]
}
...
let count = ProductCategory.allValues.count
Jest to również pomocne, gdy chcesz wyliczyć wartości i działa dla wszystkich typów Enum
static let count = allValues.count
. W allValues
razie potrzeby możesz ustawić prywatny.
Jeśli implementacja nie ma nic przeciwko używaniu wyliczeń całkowitych, możesz dodać dodatkową wartość członka wywoływaną w Count
celu reprezentowania liczby członków w wyliczeniu - patrz przykład poniżej:
enum TableViewSections : Int {
case Watchlist
case AddButton
case Count
}
Teraz możesz uzyskać liczbę członków w wyliczeniu, dzwoniąc, TableViewSections.Count.rawValue
co zwróci 2 dla powyższego przykładu.
Podczas obsługi wyliczenia w instrukcji switch upewnij się, że rzuciłeś błąd asercji, gdy napotkasz Count
członka, którego się nie spodziewasz:
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!")
}
}
Ten rodzaj funkcji może zwrócić liczbę twojego wyliczenia.
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
jest również Hashable
tego samego typu.
Wyliczanie ciągów z indeksem
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)!
}
}
liczyć: eEventTabType.allValues.count
indeks: objeEventTabType.index
Cieszyć się :)
Hej wszystkim, a co z testami jednostkowymi?
func testEnumCountIsEqualToNumberOfItemsInEnum() {
var max: Int = 0
while let _ = Test(rawValue: max) { max += 1 }
XCTAssert(max == Test.count)
}
W połączeniu z rozwiązaniem Antonio:
enum Test {
case one
case two
case three
case four
static var count: Int { return Test.four.hashValue + 1}
}
w głównym kodzie daje O (1) plus dostajesz test negatywny, jeśli ktoś doda przypadek wyliczenia five
i nie zaktualizuje implementacji count
.
Ta funkcja opiera się na 2 nieudokumentowanych bieżących zachowaniach (Swift 1.1) enum
:
enum
jest tylko indeksem case
. Jeśli liczba przypadków wynosi od 2 do 256, jest to UInt8
.enum
rzutowano bit z niepoprawnego indeksu wielkości liter, hashValue
to jest0
Korzystaj więc na własne ryzyko :)
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")
}
}
Stosowanie:
enum Foo:String {
case C000 = "foo"
case C001 = "bar"
case C002 = "baz"
}
enumCaseCount(Foo) // -> 3
Napisałem proste rozszerzenie, które daje wszystkim wyliczeniom, w których surową wartością jest liczba całkowita, count
właściwość:
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())
}
}
Niestety daje count
właściwość OptionSetType
tam, gdzie nie będzie działać poprawnie, więc oto inna wersja, która wymaga jawnej zgodności z CaseCountable
protokołem dla każdego wyliczenia, które przypadki chcesz policzyć:
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())
}
}
Jest bardzo podobny do podejścia opublikowanego przez Toma Pelaia, ale działa ze wszystkimi typami liczb całkowitych.
Oczywiście nie jest dynamiczny, ale do wielu zastosowań możesz sobie poradzić dzięki statycznemu var dodanemu do twojego Enum
static var count: Int{ return 7 }
a następnie użyj go jako EnumName.count
enum EnumNameType: Int {
case first
case second
case third
static var count: Int { return EnumNameType.third.rawValue + 1 }
}
print(EnumNameType.count) //3
LUB
enum EnumNameType: Int {
case first
case second
case third
case count
}
print(EnumNameType.count.rawValue) //3
* W Swift 4.2 (Xcode 10) można używać:
enum EnumNameType: CaseIterable {
case first
case second
case third
}
print(EnumNameType.allCases.count) //3
W moim przypadku użycia w bazie kodu, w której wiele osób może dodawać klucze do wyliczenia, a wszystkie te przypadki powinny być dostępne we właściwości allKeys, ważne jest, aby wszystkie klucze były sprawdzane względem kluczy w tym wyliczeniu. Ma to na celu uniknięcie sytuacji, gdy ktoś zapomni dodać swój klucz do listy wszystkich kluczy. Dopasowanie liczby tablicy allKeys (najpierw utworzonej jako zestaw, aby uniknąć duplikatów) do liczby kluczy w wyliczeniu zapewnia, że wszystkie są obecne.
Niektóre z powyższych odpowiedzi pokazują sposób osiągnięcia tego w Swift 2, ale żadna nie działa w Swift 3 . Oto sformatowana wersja 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)
}
W zależności od przypadku użycia możesz po prostu uruchomić test w fazie rozwoju, aby uniknąć narzutu związanego z używaniem wszystkich klawiszy na każde żądanie
Dlaczego sprawiasz, że wszystko jest tak skomplikowane? Najprostszym licznikiem Int enum jest dodanie:
case Count
Na końcu. I ... altówka - teraz masz liczyć - szybko i prosto
0
i nie miały żadnych przerw w sekwencji.
Jeśli nie chcesz opierać kodu na ostatnim wyliczeniu, możesz utworzyć tę funkcję w swoim wyliczeniu.
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
}
Wersja Swift 3 działająca z Int
wyliczeniami typów:
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
}
}
Kredyty: Na podstawie odpowiedzi Bzz i Nate Cook.
Rodzajowy IntegerType
(w Swift 3 przemianowany na Integer
) nie jest obsługiwany, ponieważ jest to mocno rozdrobniony typ ogólny, który nie ma wielu funkcji. successor
nie jest już dostępny w Swift 3.
Pamiętaj, że komentarz od Code Commander do odpowiedzi Nate Cooks jest nadal aktualny:
To miłe, ponieważ nie musisz kodować wartości na stałe, spowoduje to utworzenie każdej wartości wyliczenia za każdym razem, gdy zostanie wywołana. To jest O (n) zamiast O (1).
O ile wiem, obecnie nie ma możliwości obejścia tego problemu jako rozszerzenia protokołu (i braku implementacji w każdym wyliczeniu, jak to zrobił Nate Cook) z powodu statycznych właściwości przechowywanych, które nie są obsługiwane w typach ogólnych.
W każdym razie dla małych wyliczeń nie powinno to stanowić problemu. Typowy przypadek użycia byłby section.count
dla UITableViews
jak już wspomniano przez Zorayr.
Rozszerzając odpowiedź Matthieu Rieglera, jest to rozwiązanie dla Swift 3, które nie wymaga użycia ogólnych, i można je łatwo wywołać za pomocą typu wyliczeniowego z 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
}
Rozwiązałem ten problem dla siebie, tworząc protokół (EnumIntArray) i globalną funkcję narzędziową (enumIntArray), które bardzo łatwo dodają zmienną „All” do dowolnego wyliczenia (używając swift 1.2). Zmienna „all” będzie zawierać tablicę wszystkich elementów w wyliczeniu, dzięki czemu można użyć all.count do zliczenia
Działa tylko z wyliczeniami, które używają surowych wartości typu Int, ale być może może stanowić inspirację dla innych typów.
Zajmuje się także „luką w numeracji” i „nadmiernym czasem na iterację” problemów, które przeczytałem powyżej i gdzie indziej.
Chodzi o to, aby dodać protokół EnumIntArray do enum, a następnie zdefiniować zmienną statyczną „all”, wywołując funkcję enumIntArray i podając jej pierwszy element (i ostatni, jeśli w numeracji występują luki).
Ponieważ zmienna statyczna jest inicjalizowana tylko raz, narzut związany z przechodzeniem przez wszystkie wartości surowe uderza w twój program tylko raz.
przykład (bez przerw):
enum Animals:Int, EnumIntArray
{
case Cat=1, Dog, Rabbit, Chicken, Cow
static var all = enumIntArray(Animals.Cat)
}
przykład (z przerwami):
enum Animals:Int, EnumIntArray
{
case Cat = 1, Dog,
case Rabbit = 10, Chicken, Cow
static var all = enumIntArray(Animals.Cat, Animals.Cow)
}
Oto kod, który go implementuje:
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
}
Lub możesz po prostu zdefiniować _count
wyliczenie zewnętrzne i dołączyć je statycznie:
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
}
W ten sposób, bez względu na to, ile utworzysz wyliczeń, zostanie ono utworzone tylko raz.
(jeśli static
tak, usuń tę odpowiedź )
Poniższa metoda pochodzi z CoreKit i jest podobna do odpowiedzi sugerowanych przez innych. Działa to z 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
}
Musisz po prostu zadzwonić 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
ale zachowaj ostrożność podczas używania na typach innych niż wyliczanie. Niektóre obejście może być:
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
Może używać stałej statycznej, która zawiera ostatnią wartość wyliczenia plus jeden.
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()
}
}
}
Jest to niewielkie, ale myślę, że lepszym rozwiązaniem O (1) byłoby ( TYLKO jeśli twoje wyliczenie Int
zaczyna się od x itp.):
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
}
Aktualnie wybrana odpowiedź, którą nadal uważam, jest najlepszą odpowiedzią dla wszystkich wyliczeń, chyba że pracujesz, Int
to polecam to rozwiązanie.
guard
jest przeciw COUNT
i generuje błąd, zwraca wartość false itp., Aby rozwiązać problem dotyczący reprezentacji typów.