Как определить количество случаев в перечислении 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
, тогда я рекомендую это решение.