如何获得Swift枚举的计数?


Answers:


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?
马丁R

抱歉,我忘了提到“ DAFFlow”是一个简单的枚举,不能继承任何其他协议。–
萨蒂扬(Satyam)

1
这是最好的解决方案,但只是为了清楚起见-苹果开发人员只有在Xcode 10(因此是Swift 4.2)脱离beta版本(很可能在2018年9月14日左右)后才能真正开始使用它。
JosephH '18

1
@DaniSpringer:您可以在github.com/apple/swift-evolution/blob/master/proposals/…中找到血腥细节。但是通常由于编译器会自动推断类型,因此您不需要显式地使用该类型。
马丁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是一个很好的解决方案。我希望稍作修改。将静态count属性转换为静态方法countCases()并将其分配给静态常量caseCount,该实例是惰性的,可通过重复调用提高性能。
汤姆·佩莱亚

2
@ShamsAhmed:将计算的var转换为静态变量。
Nate Cook

3
如果您错过了枚举值,该怎么办?例如case A=1, B=3
Sasho

2
除了enum具有Int您忘记提及的原始值外,还有两个假设:具有Int原始值的Swift枚举不必从0开始(即使这是默认行为),并且它们的原始值可以是任意的,而不必增加1(即使这是默认行为)。
大卫Pásztor

90

Xcode 10更新

CaseIterable在枚举中采用协议,它提供了一个静态allCases属性,其中包含所有枚举用例Collection。只需使用其count属性即可知道枚举有多少种情况。

请参阅马丁的答案作为示例(并认可他的答案,而不是我的答案)


警告:以下方法似乎不再起作用。

我不知道有任何通用方法来计算枚举案例的数量。但是,我注意到hashValue枚举案例的属性是增量的,从零开始,并且顺序由声明案例的顺序确定。因此,最后一个枚举的哈希值加1对应于案例数。

例如,此枚举:

enum Test {
    case ONE
    case TWO
    case THREE
    case FOUR

    static var count: Int { return Test.FOUR.hashValue + 1}
}

count 返回4。

我不能说这是否是规则,或者将来是否会改变,因此使用后果自负:)


48
由未记录的功能生存,由未记录的功能死亡。我喜欢!
Nate Cook

9
我们不应该真正依靠hashValues这些东西。我们所知道的是,它是一个随机的唯一值-将来可能会很容易更改,具体取决于某些编译器实现细节;但总体而言,缺少内置计数功能令人不安。
佐拉尔

16
如果您不介意显式设置case ONE = 0,则可以替换hashValuerawValue
凯文·齐

3
这里的关注点是使用hashValue的未记录属性,因此我的建议是使用rawValue的记录属性。
奇文

7
您已经硬编码了哪个常数是最高值的事实,更好地,更安全地使用类似的东西static var count = 4而不是将命运留在Swift的未来实现方式中
Dale

72

我定义了一个可重用的协议,该协议根据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)

1
尼斯和优雅,应该被接受的答案恕我直言
shannoga

1
也许最好更改count++为,count+=1因为++注释将在Swift 3中删除
Aladin

1
不能只用相同的方法static var caseCount: Int { get }吗?为什么需要static func
pxpgraphics

如果您错过了枚举值,该怎么办?例如case A=1, B=3
Sasho

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类型


尽管不如扩展程序解决方案那么优雅,而且非常手册,但我认为这是最有用的,因为它提供了远远超过计数的功能。它为您提供值的顺序和所有值的列表。
Nader Eloshaiker

2
您还可以通过执行将计数添加到枚举static let count = allValues.count。然后,您可以allValues根据需要将其设为私有。
ThomasW

15

如果实现不反对使用整数枚举,则可以添加一个额外的成员值,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!")
  }
}

我喜欢这种解决方案,因为它在添加更多枚举值时会自动更改计数。但是请记住,当枚举的rawValues 0开始这仅适用
joern

2
同意,有两个限制:必须是整数枚举,并且必须从零开始并逐步递增。
Zorayr

3
我认为Swift更强大的枚举的全部要点是,我们不必使用与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
这不再适用于Swift3。尝试制定适当的实现,但是空了
Cody Winton

这将是非常unfun调试如果紧邻的端部的存储器地址enum Hashable相同类型的。
NRitH

10

带索引的字符串枚举

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是从无效案例索引进行位广播的,则其hashValue0

所以使用后果自负:)

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

在发布和即席申请时将崩溃
HotJard

这确实可以在模拟器中使用,但不能在真正的64位设备上使用。
Daniel Nord

5

我写了一个简单的扩展,为原始值是整数的所有枚举赋予一个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())
    }
}

它与Tom Pelaia发布的方法非常相似,但适用于所有整数类型。


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

*在Swift 4.2(Xcode 10)上可以使用:

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

print(EnumNameType.allCases.count) //3

2

对于我的用例,在一个代码库中,可能有多个人向枚举添加键,并且这些用例都应在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的开销。


2

你为什么使这一切变得如此复杂?Int枚举的SIMPLEST计数器要添加:

case Count

到底。还有...中提琴-现在您可以算数-快速而简单


1
这a)添加了一个多余的枚举大小写,并且b)如果枚举的原始类型不是Int,则将不起作用。
罗伯特·阿特金斯

这实际上不是一个坏答案。就像上面的@Tom Pelaia的答案一样,它要求原始值从头开始,0并且序列中没有间隔。
NRitH

1

如果您不想将代码基于上一个枚举,则可以在枚举内创建此函数。

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

一个斯威夫特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那样在每个枚举中实现)目前尚无解决方法。

无论如何,对于小的枚举,这应该没有问题。如Zorayr所述,典型的用例是section.countfor UITableViews


1

扩展了Matthieu Riegler的答案,这是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),为自己解决了这个问题,可以很容易地向任何枚举添加“ All”变量(使用swift 1.2)。“ all”变量将包含枚举中所有元素的数组,因此您可以使用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
向您的枚举添加一个实际上不代表枚举类型的值,这是一种不好的代码味道。我发现即使有时包含一个“ ALL”或“ NONE”,也很难证明其合理性。包括“ COUNT”只是为了解决这个问题,这是非常糟糕的。
迈克尔·彼得森

1
臭吗 如果您要确定的话。表演者?是。由开发人员决定优缺点。这实际上与Zorayr的答案相同(在上面他对其进行了更详细的介绍),并且新接受的答案也相似。但是直到swift为它添加了一个api;这就是我们中某些人决定使用的。您可以添加验证枚举值的函数guard一场反抗COUNT,并抛出一个错误,返回false等,以解决您的类型的代表表示关注。
Drmorgan '16
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.