Як я можу отримати підрахунок швидкості переліку?


Відповіді:


173

Станом на Swift 4.2 (Xcode 10) ви можете заявити про відповідність CaseIterableпротоколу, це працює для всіх перерахувань без пов'язаних значень:

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

Кількість справ тепер просто отримують

print(Stuff.allCases.count) // 4

Для отримання додаткової інформації див


1
В останній версії swift помилка кидка "Тип" DAFFlow "не відповідає протоколу" RawRepresentable "". Чому мене змушують дотримуватися цього? Будь-яка ідея?
— Сатьям

@Satyam: Що таке DAFFlow?
— Мартін Р

вибачте, що забув згадати, що "DAFFlow" - це проста перерахунок, який не успадковується жодним іншим протоколом.
— Satyam,

1
Це найкраще рішення, але тільки для наочності - розробники Apple дійсно зможуть почати використовувати це лише після виходу бета-версії Xcode 10 (а значить, і Swift 4.2) (так, швидше за все, 14 вересня 2018 року).
— ДжозефH

1
@DaniSpringer: деталі горі ви знайдете на сайті github.com/apple/swift-evolution/blob/master/proposals/… . Але зазвичай цей тип вам не потрібен явно через автоматичне виведення типу компілятора.
— Martin R

143

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

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

16
Хоча приємно, тому що вам не потрібно вводити значення коду, але це створюватиме значення кожного перерахунку щоразу, коли воно викликається. Тобто O (n) замість O (1). :(
— Командир коду

4
Це хороше рішення для суміжних Int. Я віддаю перевагу невеликій модифікації. Перетворіть властивість статичного підрахунку в статичний метод countCases () та призначте його статичній постійній caseCount, яка ледача та покращує продуктивність при повторних викликах.
— Том Пелайя

2
@ShamsAhmed: Перетворив обчислений var у статичний.
— Нейт Кук

3
Що робити, якщо ви втрачаєте якусь цінність у перерахунку? наприклад case A=1, B=3?
— Сашо

2
Існує 2 припущення, окрім того enum, Intщо ви маєте неодноразове значення, яке ви забули згадати: Swift enum з Int необробленими значеннями не потрібно починати з 0 (навіть якщо це поведінка за замовчуванням), і їхні вихідні значення можуть бути довільними, вони не мають приріст на 1 (хоча це поведінка за замовчуванням).
— Давид Паштор

90

Оновлення 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.

Я не можу сказати, чи це правило, чи це колись зміниться в майбутньому, тому використовуйте на свій страх і ризик :)


48
Живіть за недокументованою ознакою, помирайте від недокументованої ознаки. Мені це подобається!
— Нейт Кук

9
Ми не повинні покладатися на hashValuesці речі; все, що ми знаємо, це те, що це якесь випадкове унікальне значення - воно може дуже легко змінюватися в майбутньому залежно від деталей реалізації компілятора; але загалом відсутність вбудованої функціональної можливості підрахунку заважає.
— Зорайр

16
Якщо ви не заперечуєте проти явного налаштування case ONE = 0, його можна замінити hashValueна rawValue.
— Кевін Ци

3
Проблема тут полягає у використанні незадокументованої властивості hashValue, тому моя пропозиція полягає у використанні документально підтвердженої властивості rawValue.
— Кевін Ци

7
Ви вже твердо зафіксували той факт, яка константа є найвищою цінністю, Краще і безпечніше просто використовувати щось на кшталт, static var count = 4а не залишати свою долю в долі майбутніх реалізацій Swift
— Дейл

72

Я визначаю протокол багаторазового використання, який автоматично виконує підрахунок справ на основі підходу, розміщеного Нейт Куком.

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)

1
Приємно та елегантно, має бути прийнята відповідь ІМХО
— shannoga

1
може бути , це краще для зміни count++до count+=1так ++позначення будуть видалені в Swift 3
— Aladin

1
хіба не можна було б зробити те саме static var caseCount: Int { get }? чому потреба в static func?
— pxpgraphics

Що робити, якщо ви втрачаєте якусь цінність у перерахунку? наприклад case A=1, B=3?
— Сашо

1
@Sasho, тоді це не працюватиме. Це вимагає, щоб ваші випадки починалися 0і не мали прогалин.
— NRitH

35

Створіть статичний масив allValues, як показано у цій відповіді

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

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

...

let count = ProductCategory.allValues.count

Це також корисно, коли ви хочете перерахувати значення, і працює для всіх типів Enum


Хоча це не настільки елегантно, як розширення для розширень і дуже ручне, я вважаю, що це найкорисніше, оскільки пропонує набагато більше, ніж рахувати. Він дає вам порядок значень та список усіх значень.
— Надер Елошайкер

2
Ви також можете додати рахунок до перерахунків, зробивши це static let count = allValues.count. Тоді ви можете зробити allValuesприватне, якщо бажаєте.
— ThomasW

15

Якщо реалізація не має нічого проти використання цілочисленних перерахунків, ви можете додати значення додаткового члена, викликаного 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!")
  }
}

Мені подобається це рішення, оскільки воно автоматично змінює підрахунок, додаючи більше значень перерахувань. Однак майте на увазі, що це працює лише тоді, коли вихідні значення перерахунків починаються з 0.
— joern

2
Погодьтеся, є два обмеження: повинно бути ціле число, а воно повинно починатися з нуля і продовжуватись поступово.
— Зорайр

3
Я подумав, що вся суть сильніших переліків Свіфта полягає в тому, що нам не доведеться використовувати ті самі хаки, які ми використовували в Objective-C: /
— pkamb

14

Цей вид функції здатний повернути кількість вашого перерахунку.

Швидкий 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
   }

3
Це більше не працює для Swift 3. Намагається розробити належну реалізацію, але виходить порожнім
— Коді Вінтон

Це налагодження буде дуже неправомірним, якщо адреса пам'яті, що безпосередньо примикає до кінця enum, також Hashable є одного типу.
— NRitH

10

Рядок 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

Насолоджуйтесь :)


10

Ой, всі, що робити з одиничними тестами?

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.


7

Ця функція спирається на 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

У програмі випуску та adhoc відбудеться CRASH
— HotJard

Це працює в тренажері, але не на реальному 64-бітному пристрої.
— Даніель Норд

5

Я написав просте розширення, яке дає всі перерахунки, де значенням 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())
    }
}

Це дуже схоже на підхід, опублікований Томом Пелая, але працює з усіма цілими типами.


4

Звичайно, це не динамічно, але для багатьох застосувань ви можете отримати статичний var, доданий до вашого Enum

static var count: Int{ return 7 }

а потім використовувати його як EnumName.count


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

* On Swift 4.2 (Xcode 10) може використовувати:

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

print(EnumNameType.allCases.count) //3

2

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

Залежно від випадку використання, ви можете просто запустити тест у розробці, щоб уникнути накладних витрат на використання всіх ключів на кожен запит


2

Чому ви робите це все так складно? Найпростіший лічильник Int enum повинен додати:

case Count

В кінці. І ... віола - тепер у вас є кількість - швидка і проста


1
Це a) додає сторонній випадок enum і b) не працюватиме, якщо тип сировини enum є іншим, ніж Int.
— Роберт Аткінс

Це насправді не є поганою відповіддю. Як і у відповіді @Tom Pelaia вище, вона вимагає, щоб початкові значення починалися з початку 0і не мали прогалин у послідовності.
— NRitH

1

Якщо ви не хочете базувати свій код в останньому перерахунку, ви можете створити цю функцію всередині вашого 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
}

1

Версія 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.


1

Розширюючи відповідь Матьє Ріглера, це рішення для 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
}

0

Я вирішив цю проблему для себе, створивши протокол (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   
}

0

Або ви можете просто визначити _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це зробити)


0

Наступний метод походить від 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.


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

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

-1

Він може використовувати статичну константу, яка містить останнє значення перерахунку плюс одне.

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

-3

Це незначне значення, але я думаю, що кращим рішенням 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я рекомендую це рішення.


3
Додавання значення до перерахунку, яке насправді не представляє тип перерахунку, є поганим запахом коду. Мені важко навіть виправдати, включаючи "ВСІ" або "НІКОЛІ", хоча це може часом бути спокусливим. Включення "COUNT", щоб просто зламати цю проблему, дуже смердюче.
— Майкл Петерсон

1
Смірна? Якщо ви хочете назвати це точно. Виконавець? Так. Виробники повинні вирішити плюси і мінуси. Це насправді та сама відповідь на відповідь Зорайра вище, де він детальніше розбирається про це, і нова прийнята відповідь також схожа. Але поки Швидкий додає для цього апі; це те, що деякі з нас вирішили використати. Ви можете додати функцію, яка перевіряє значення enum, яке guardпроти, COUNTі видає помилку, повертає false та ін., Щоб вирішити ваше занепокоєння щодо представлення типів.
— Дрморган
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.