【发布时间】:2015-01-21 13:51:26
【问题描述】:
如何确定 Swift 枚举中的事例数?
(我想避免使用manually enumerating through all the values,或者尽可能使用旧的“enum_count trick”。)
【问题讨论】:
如何确定 Swift 枚举中的事例数?
(我想避免使用manually enumerating through all the values,或者尽可能使用旧的“enum_count trick”。)
【问题讨论】:
从 Swift 4.2 (Xcode 10) 开始,您可以声明
符合CaseIterable 协议,这适用于所有人
没有关联值的枚举:
enum Stuff: CaseIterable {
case first
case second
case third
case forth
}
现在可以简单地获得案例数
print(Stuff.allCases.count) // 4
有关详细信息,请参阅
【讨论】:
我有a blog post 对此进行了更详细的说明,但只要您的枚举的原始类型是整数,您就可以通过这种方式添加计数:
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 原始值之外还有 2 个假设,但您忘了提及:带有 Int 原始值的 Swift 枚举不必从 0 开始(尽管这是默认行为) 并且它们的原始值可以是任意的,它们不必增加 1(即使这是默认行为)。
Xcode 10 更新
在枚举中采用CaseIterable 协议,它提供了一个静态的allCases 属性,其中包含所有枚举情况作为Collection。只需使用其count 属性即可知道枚举有多少个案例。
请参阅 Martin 的答案以获取示例(并支持他的答案而不是我的答案)
警告:以下方法似乎不再有效。
我不知道有任何通用方法来计算枚举案例的数量。但是,我注意到枚举案例的 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 实现的命运
我定义了一个可重用的协议,它会根据 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)
【讨论】:
count++ 更改为count+=1,因为++ 符号将在Swift 3 中被删除
static var caseCount: Int { get }吗?为什么需要static func?
case A=1, B=3 ?
0 开始并且没有间隔。
创建静态 allValues 数组,如 answer 所示
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
}
现在您可以通过调用 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
}
【讨论】:
enum末尾的内存地址是也Hashable的同一类型,这将是非常不愉快的调试。
带索引的字符串枚举
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 是从 invalid 案例索引位转换的,则其 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
【讨论】:
我写了一个简单的扩展,它为所有原始值为整数的枚举提供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 发布的方法非常相似,但适用于所有整数类型。
【讨论】:
当然,它不是动态的,但对于许多用途,您可以通过添加到 Enum 的静态 var 来解决
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 的开销
【讨论】:
你为什么把事情搞得这么复杂? Int 枚举的最简单的计数器是添加:
case Count
最后。还有...中提琴 - 现在你有数了 - 快速而简单
【讨论】:
0 开始,并且在序列中没有间隙。
如果您不想将代码基于最后一个枚举,您可以在枚举中创建此函数。
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
}
【讨论】:
使用Int 类型枚举的Swift 3 版本:
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 对应于 Zorayr 已经提到的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
}
【讨论】:
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)")
【讨论】:
我通过创建一个协议 (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
}
【讨论】:
或者你可以在枚举之外定义_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即可。
【讨论】:
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) 解决方案如下(仅如果您的枚举是从 x 开始的 Int 等):
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,否则我推荐此解决方案。
【讨论】:
guards 与 COUNT 的枚举值并引发错误、返回 false 等,以解决您对类型表示的担忧。