Як я можу визначити кількість випадків у перерахунку 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
}()
}
case A=1, B=3
?
enum
, Int
що ви маєте неодноразове значення, яке ви забули згадати: Swift enum з Int необробленими значеннями не потрібно починати з 0 (навіть якщо це поведінка за замовчуванням), і їхні вихідні значення можуть бути довільними, вони не мають приріст на 1 (хоча це поведінка за замовчуванням).
Оновлення Xcode 10
Прийняти CaseIterable
протокол в enum, він надає статичну allCases
властивість, яка містить усі випадки перерахунків як a Collection
. Просто використовуйте його count
властивість, щоб знати, скільки випадків має перерахунок.
Для прикладу див. Відповідь Мартіна (і підкресліть його відповіді, а не мої)
Попередження : метод, наведений нижче, вже не працює.
Мені не відомий жоден загальний метод підрахунку кількості випадків перерахування. Однак я помітив, що hashValue
властивість випадків перерахунків наростає, починаючи з нуля, і в порядку, визначеному порядком, в якому заявлені справи. Отже, хеш останнього перерахунку плюс один відповідає кількості випадків.
Наприклад, із цим перерахунком:
enum Test {
case ONE
case TWO
case THREE
case FOUR
static var count: Int { return Test.FOUR.hashValue + 1}
}
count
повертає 4.
Я не можу сказати, чи це правило, чи це колись зміниться в майбутньому, тому використовуйте на свій страх і ризик :)
hashValues
ці речі; все, що ми знаємо, це те, що це якесь випадкове унікальне значення - воно може дуже легко змінюватися в майбутньому залежно від деталей реалізації компілятора; але загалом відсутність вбудованої функціональної можливості підрахунку заважає.
case ONE = 0
, його можна замінити hashValue
на rawValue
.
static var count = 4
а не залишати свою долю в долі майбутніх реалізацій Swift
Я визначаю протокол багаторазового використання, який автоматично виконує підрахунок справ на основі підходу, розміщеного Нейт Куком.
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)
count++
до count+=1
так ++
позначення будуть видалені в Swift 3
static var caseCount: Int { get }
? чому потреба в static func
?
case A=1, B=3
?
0
і не мали прогалин.
Створіть статичний масив allValues, як показано у цій відповіді
enum ProductCategory : String {
case Washers = "washers", Dryers = "dryers", Toasters = "toasters"
static let allValues = [Washers, Dryers, Toasters]
}
...
let count = ProductCategory.allValues.count
Це також корисно, коли ви хочете перерахувати значення, і працює для всіх типів Enum
static let count = allValues.count
. Тоді ви можете зробити allValues
приватне, якщо бажаєте.
Якщо реалізація не має нічого проти використання цілочисленних перерахунків, ви можете додати значення додаткового члена, викликаного Count
для представлення кількості членів в перерахунку - див. Приклад нижче:
enum TableViewSections : Int {
case Watchlist
case AddButton
case Count
}
Тепер ви можете отримати кількість членів в enum, зателефонувавши, TableViewSections.Count.rawValue
який поверне 2 для прикладу вище.
Коли ви обробляєте перерахунок в операторі перемикання, переконайтеся, що викинете невдачу твердження при зустрічі з 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
, також Hashable
є одного типу.
Рядок Enum з покажчиком
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), плюс ви отримуєте невдалий тест, якщо хтось додає облік 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
Я написав просте розширення, яке дає всі перерахунки, де значенням raw є ціле 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 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
* On Swift 4.2 (Xcode 10) може використовувати:
enum EnumNameType: CaseIterable {
case first
case second
case third
}
print(EnumNameType.allCases.count) //3
У моєму випадку використання, в кодовій базі, де кілька людей могли додавати ключі до перерахунку, і ці випадки повинні бути доступними у властивості allKeys, важливо, щоб всіKeys були перевірені щодо ключів у перерахунку. Це потрібно, щоб хтось не забув додати свій ключ до списку всіх клавіш. Зіставлення підрахунку масиву 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)
}
Залежно від випадку використання, ви можете просто запустити тест у розробці, щоб уникнути накладних витрат на використання всіх ключів на кожен запит
Чому ви робите це все так складно? Найпростіший лічильник Int enum повинен додати:
case Count
В кінці. І ... віола - тепер у вас є кількість - швидка і проста
0
і не мали прогалин у послідовності.
Якщо ви не хочете базувати свій код в останньому перерахунку, ви можете створити цю функцію всередині вашого enum.
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.
Майте на увазі, що коментар від командира коду до відповіді Nate Cooks залишається дійсним:
Хоча приємно, тому що вам не потрібно вводити значення коду, але це створюватиме значення кожного перерахунку щоразу, коли воно викликається. Тобто O (n) замість O (1).
Наскільки я знаю, в даний час немає жодного вирішення при використанні цього в якості розширення протоколу (а не реалізація в кожному переліку, як це робив Нейт Кук) через статичні збережені властивості, які не підтримуються в загальних типах.
У всякому разі, для невеликих переживань це не повинно бути проблемою. Типовий випадок використання буде правильним section.count
для UITableViews
як уже згадано Zorayr.
Розширюючи відповідь Матьє Ріглера, це рішення для 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
}
Я вирішив цю проблему для себе, створивши протокол (EnumIntArray) та глобальну функцію утиліти (enumIntArray), які дозволяють дуже просто додати змінну "Усі" до будь-якої перерахунку (використовуючи швидкий 1.2). Змінна "all" міститиме масив усіх елементів в enum, щоб ви могли використовувати all.count для підрахунку
Він працює лише з перерахунками, які використовують вихідні значення типу Int, але, можливо, він може надати натхнення для інших типів.
Він також стосується питань "розриву в нумерації" та "зайвого часу для повторення", про які я читав вище та в інших місцях.
Ідея полягає в тому, щоб додати до свого переліку протокол EnumIntArray, а потім визначити статичну змінну "всі", викликавши функцію 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
зовнішній перелік і прикріпити його статично:
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()
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
але будьте обережні з використанням для не перелічених типів. Деякі шляхи вирішення можуть бути:
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
я рекомендую це рішення.
guard
проти, COUNT
і видає помилку, повертає false та ін., Щоб вирішити ваше занепокоєння щодо представлення типів.