Как я могу получить количество перечислений Swift?

Как я могу определить количество случаев в перечислении Swift?

(Я бы хотел избежать ручного перечисления всех значений или использования старого трюка enum_count, если это возможно.)

27 ответов

Решение

Начиная с Swift 4.2 (Xcode 10) вы можете заявить о соответствии CaseIterable протокол, это работает для всех перечислений без связанных значений:

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

Количество случаев теперь просто получается с

print(Stuff.allCases.count) // 4

Для получения дополнительной информации см.

У меня есть запись в блоге, в которой более подробно об этом говорится, но до тех пор, пока необработанный тип вашего enum является целым числом, вы можете добавить счет следующим образом:

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

Я не знаю ни одного общего метода для подсчета количества перечислений. Я заметил, однако, что hashValue свойство перечислимых дел является инкрементным, начиная с нуля, и в порядке, определяемом порядком, в котором объявлены дела. Итак, хэш последнего перечисления плюс один соответствует числу падежей.

Например, с этим перечислением:

enum Test {
    case ONE
    case TWO
    case THREE
    case FOUR

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

count возвращает 4.

Я не могу сказать, если это правило или оно когда-нибудь изменится, так что используйте на свой страх и риск:)

Я определяю протокол многократного использования, который автоматически выполняет подсчет случаев на основе подхода, опубликованного 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
    }
}

Затем я могу использовать этот протокол, например, следующим образом:

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

Создайте статический массив allValues, как показано в этом ответе

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

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

...

let count = ProductCategory.allValues.count

Это также полезно, когда вы хотите перечислить значения, и работает для всех типов Enum

Если реализация не имеет ничего против использования целочисленных перечислений, вы можете добавить дополнительное значение члена, называемое Count для представления количества членов в перечислении - см. пример ниже:

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

Теперь вы можете узнать количество участников в списке, позвонив TableViewSections.Count.rawValue который вернет 2 для примера выше.

Когда вы обрабатываете перечисление в операторе switch, убедитесь, что вы выбросили ошибку подтверждения при обнаружении Count член, где вы этого не ожидаете:

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

Эта функция может вернуть счет вашего перечисления.

Свифт 2:

func enumCount<T: Hashable>(_: T.Type) -> Int {
    var i = 1
    while (withUnsafePointer(&i) { UnsafePointer<T>($0).memory }).hashValue != 0 {
        i += 1
    }
    return i
}

Свифт 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 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)!
    }
}

считать: eEventTabType.allValues.count

индекс: objeEventTabType.index

Наслаждаться:)

О, всем, а как насчет юнит-тестов?

func testEnumCountIsEqualToNumberOfItemsInEnum() {

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

    XCTAssert(max == Test.count)
}

Это в сочетании с решением Антонио:

enum Test {

    case one
    case two
    case three
    case four

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

в основном коде дает вам O(1) плюс вы получаете провальный тест, если кто-то добавляет регистр enum five и не обновляет реализацию count,

Эта функция опирается на 2 недокументированных тока(Swift 1.1) enum поведение:

  • Расположение памяти enum это просто индекс case, Если число случаев от 2 до 256, это UInt8,
  • Если enum был приведен к битам из неверного индекса, его hashValue является 0

Так что пользуйтесь на свой страх и риск:)

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

Использование:

enum Foo:String {
    case C000 = "foo"
    case C001 = "bar"
    case C002 = "baz"
}
enumCaseCount(Foo) // -> 3
enum EnumNameType: Int {
    case first
    case second
    case third

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

print(EnumNameType.count) //3

ИЛИ ЖЕ

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

print(EnumNameType.count.rawValue) //3

* На Swift 4.2 (Xcode 10) можно использовать:

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

print(EnumNameType.allCases.count) //3

Я написал простое расширение, которое дает все перечисления, где необработанное значение является целым числом 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())
    }
}

К сожалению, это дает count собственность на OptionSetType где это не будет работать должным образом, так что вот еще одна версия, которая требует явного соответствия CaseCountable протокол для любого перечисления, какие случаи вы хотите считать:

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

Это очень похоже на подход, опубликованный Томом Пелайей, но работает со всеми целочисленными типами.

Конечно, это не динамично, но для многих целей вы можете обойтись с помощью статической переменной, добавленной в ваш Enum.

static var count: Int{ return 7 }

а затем использовать его как EnumName.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)")

В моем случае использования в кодовой базе, где несколько человек могли добавлять ключи в перечисление, и все эти случаи должны быть доступны в свойстве allKeys, важно, чтобы все ключи проверялись на соответствие ключам в перечислении. Это сделано для того, чтобы не забыть добавить свой ключ в список всех ключей. Сопоставление числа массива allKeys (сначала созданного как набор, чтобы избежать дублирования) с количеством ключей в перечислении гарантирует, что они все присутствуют.

Некоторые из ответов выше показывают способ достижения этого в Swift 2, но ни один не работает в Swift 3. Вот отформатированная версия 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)
}

В зависимости от вашего варианта использования, вы можете просто запустить тестирование в процессе разработки, чтобы избежать затрат на использование allKeys при каждом запросе.

Почему ты делаешь все это таким сложным? Счетчик SIMPLEST Int enum должен добавить:

case Count

В конце. И... Альт - теперь у вас есть счет - быстро и просто

Расширяя ответ Matthieu Riegler, это решение для Swift 3, которое не требует использования шаблонов и может быть легко вызвано с помощью типа enum с 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
}

Если вы не хотите основывать свой код в последнем перечислении, вы можете создать эту функцию в своем перечислении.

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
}

Версия Swift 3, работающая с Int Тип enums:

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

Кредиты: основаны на ответах bzz и Нейта Кука.

общий IntegerType (в Swift 3 переименован в Integer) не поддерживается, так как это сильно фрагментированный обобщенный тип, в котором отсутствует множество функций. successor больше не доступен с Swift 3.

Имейте в виду, что комментарий от Code Commander к ответу Нейта Кука по-прежнему действителен:

Хотя это и хорошо, поскольку вам не нужно жестко кодировать значение, оно будет создавать экземпляры каждого значения перечисления при каждом его вызове. Это O(n) вместо O(1).

Насколько я знаю, в настоящее время нет обходного пути при использовании этого в качестве расширения протокола (и не реализуется в каждом перечислении, как это делал Нейт Кук) из-за того, что статические хранимые свойства не поддерживаются в универсальных типах.

Во всяком случае, для небольших перечислений это не должно быть проблемой. Типичный вариант использования будет section.count за UITableViews как уже упоминал Зорайр.

Мне нравится принятый ответ. Однако для тех, кто хочет жестко закодировать значение счетчика, но использует тип перечисления String, а не Int, есть простой способ:

enum Test: String {

case One = "One"
case Two = "Two"
case Three = "Three"
case Four = "Four"
case count = "4"


}

Доступ к нему

  guard let enumCount = Int(Test.count.rawValue) else { return } 

Следующий метод взят из CoreKit и похож на ответы, предложенные некоторыми другими. Это работает со 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
}

Тогда вам просто нужно просто позвонить Weekdays.allValues.count,

Просто хочу поделиться решением, когда у вас есть перечисление со связанными значениями.

      enum SomeEnum {
  case one
  case two(String)
  case three(String, Int)
}
  • CaseIterableне предоставляет allCasesавтоматически.
  • Мы не можем предоставить необработанный тип, например Intдля вашего перечисления, чтобы каким-то образом подсчитать количество случаев.

Что мы можем сделать, так это использовать силу switchа также fallthroughключевое слово.

      extension SomeEnum {
  static var casesCount: Int {
    var sum = 0

    switch Self.one { // Potential problem
       case one:
         sum += 1
         fallthrough

       case two:
         sum += 1
         fallthrough

       case three:
         sum += 1
    }

    return sum
  }
}

Итак, теперь вы можете сказать SomeEnum.casesCount.

Примечания:

  • У нас все еще есть проблема с switch Self.one {..., мы жестко закодировали первый случай. Вы можете легко взломать это решение. Но я использовал его только для модульных тестов, так что это не было проблемой.
  • Если вам часто нужно получить количество случаев в перечислениях со связанными значениями, подумайте о генерации кода.

Или вы можете просто определить _count вне перечисления и прикрепить его статически:

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
}

Таким образом, независимо от того, сколько перечислений вы создадите, он будет создан только один раз.

(удалите этот ответ, если static делает это)

Я решил эту проблему для себя, создав протокол (EnumIntArray) и глобальную служебную функцию (enumIntArray), которые очень легко добавляют переменную "All" в любое перечисление (используя swift 1.2). Переменная "all" будет содержать массив всех элементов в перечислении, так что вы можете использовать all.count для подсчета

Он работает только с перечислениями, которые используют необработанные значения типа Int, но, возможно, он может послужить источником вдохновения для других типов.

В нем также рассматриваются проблемы "разрыва в нумерации" и "чрезмерного времени для итерации", о которых я читал выше и в других местах.

Идея состоит в том, чтобы добавить протокол EnumIntArray в ваше перечисление, а затем определить статическую переменную "all", вызвав функцию enumIntArray и предоставив ей первый элемент (и последний, если в нумерации есть пробелы).

Поскольку статическая переменная инициализируется только один раз, накладные расходы на просмотр всех необработанных значений затрагивают вашу программу только один раз.

пример (без пробелов):

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

пример (с пробелами):

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

Вот код, который его реализует:

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   
}

Он может использовать статическую константу, которая содержит последнее значение перечисления плюс единицу.

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

но будьте осторожны с использованием не перечислимых типов. Некоторые обходные пути могут быть:

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

Это незначительно, но я думаю, что лучшее решение O(1) будет следующим (ТОЛЬКО если ваш enum Int начиная с х и т. д.):

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 
}

Текущий выбранный ответ, который я все еще считаю, является лучшим ответом для всех перечислений, если вы не работаете с Int тогда я рекомендую это решение.

Другие вопросы по тегам