Как определить количество случаев в перечислении Swift?
(Я хотел бы избежать вручную перечислять все значения или использовать старый трюк enum_count "если возможно.
Как определить количество случаев в перечислении Swift?
(Я хотел бы избежать вручную перечислять все значения или использовать старый трюк enum_count "если возможно.
Начиная с Swift 4.2 (Xcode 10) вы можете объявить соответствие протоколу CaseIterable, это работает для всех перечислений без связанных значений:
enum Stuff: CaseIterable {
case first
case second
case third
case forth
}
Теперь количество случаев просто получается с
print(Stuff.allCases.count) // 4
Для получения дополнительной информации см.
У меня сообщение в блоге, которое более подробно описано на этом, но пока ваш тип перечислимого типа является целым числом, вы можете добавить счет таким образом:
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
}()
}
Xcode 10 обновление
Примите протокол CaseIterable в перечислении, он предоставляет статическое свойство allCases которое содержит все перечисления в качестве Collection. Просто используйте его свойство count чтобы узнать, сколько случаев имеет перечисление.
См. Ответ Мартина в качестве примера (и опишите его ответы, а не мои)
Предупреждение: приведенный ниже метод, похоже, больше не работает.
Я не знаю ни одного общего метода для подсчета количества перечислений. Однако я заметил, что свойство 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, как показано в этом answer
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!")
}
}
Эта функция может возвращать счет вашего перечисления.
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
}
О, эй, все, как насчет модульных тестов?
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) плюс , вы получаете неудачный тест, если кто-то добавляет случай перечисления five и не обновляет реализацию count.
Перечисление строк с индексом
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)!
}
}
count: eEventTabType.allValues.count
index: objeEventTabType.index
Наслаждайтесь:)
Эта функция основана на 2 недокументированных действиях (Swift 1.1) enum:
enum - это всего лишь индекс case. Если число случаев от 2 до 256, оно UInt8.enum был отброшен в битах из недопустимого индекса case, его 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
Я написал простое расширение, которое дает все перечисления, где необработанное значение целое a 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())
}
}
Он очень похож на подход, поставленный Томом Пелаей, но работает со всеми целыми типами.
Конечно, он не динамический, но для многих применений вы можете получить статический var, добавленный в ваш Enum
static var count: Int{ return 7 }
а затем используйте его как EnumName.count
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
В моем случае использования в кодовой базе, где несколько человек могут добавлять ключи к перечислению, и все эти случаи должны быть доступны в свойстве allKeys, важно, чтобы 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
В конце концов. И... viola - теперь у вас есть счет - быстрый и простой
Если вы не хотите использовать свой код в последнем перечислении, вы можете создать эту функцию внутри своего перечисления.
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
}
A версия Swift 3, работающая с перечислением типа Int:
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 и Nate Cook.
Общий IntegerType (в Swift 3, переименованный в Integer) не поддерживается, поскольку он сильно фрагментированный родовой тип, который не имеет большого количества функций. successor больше не доступен для Swift 3.
Имейте в виду, что комментарий от Code Commander к ответу Nate Cooks остается в силе:
Хорошо, потому что вам не нужно жестко указывать значение, это будет создавать каждое значение перечисления каждый раз, когда он вызывается. Это O (n) вместо O (1).
Насколько я знаю, в настоящее время нет обходного пути при использовании этого в качестве расширения протокола (а не реализации в каждом перечислении, например, Nate Cook) из-за того, что статические хранимые свойства не поддерживаются в общих типах.
В любом случае, для небольших перечислений это не должно быть проблемой. Типичным примером использования будет section.count для UITableViews, как уже упоминалось Зорайром.
Расширение ответа Matthieu Riegler, это решение для Swift 3, которое не требует использования дженериков и может быть легко вызвано с использованием типа перечисления с помощью 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
}
Я решил эту проблему для себя, создав протокол (EnumIntArray) и глобальную функцию полезности (enumIntArray), благодаря которой очень легко добавить переменную "Все" в любое перечисление (с использованием swift 1.2). Переменная "all" будет содержать массив всех элементов в перечислении, чтобы вы могли использовать all.count для 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
}
Или вы можете просто определить _count вне enum и прикрепить его статически:
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)
Следующий метод исходит от 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 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()
печать ( "(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
но будьте осторожны с использованием неперечислимых типов. Некоторое обходное решение может быть:
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
Он может использовать статическую константу, которая содержит последнее значение перечисления плюс одно.
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()
}
}
}
Это незначительно, но я думаю, что лучшее решение O (1) будет следующим ( ТОЛЬКО, если ваше перечисление Int, начиная с x и т.д.):
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, тогда я рекомендую это решение.