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


Відповіді:


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.