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
詳細については、
これについて詳しく説明しているブログ投稿がありますが、列挙型のraw型が整数である限り、次のようにカウントを追加できます。
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 enumは0から始める必要はありません(それがデフォルトの動作ですが)、それらの生の値は任意にすることができますが、持っていません1ずつインクリメントします(デフォルトの動作ですが)。
Xcode 10アップデート
CaseIterable
列挙型のプロトコルを採用すると、allCases
すべての列挙型のケースをとして含む静的プロパティが提供されますCollection
。そのcount
プロパティを使用して、列挙型のケースの数を知るだけです。
例としてマーティンの回答を参照してください(そして私のものではなく彼の回答に賛成票を入れてください)
警告:以下の方法はもう機能していないようです。
列挙型のケースの数を数える一般的な方法は知りません。ただしhashValue
、列挙型のケースのプロパティは、ゼロから始まり、ケースが宣言されている順序によって決定される順序で増加することに気付きました。したがって、最後のenumに1を加えたハッシュは、ケースの数に対応します。
たとえば、この列挙型の場合:
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
ではなく、スウィフトの将来の実装の運命にあなたの運命を残して
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
、次のように変更++
すること
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
}
これで、呼び出しによって列挙型のメンバー数を取得できますTableViewSections.Count.rawValue
。上記の例では2を返します。
switchステートメントでenumを処理しているときは、予期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 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
IS0
だからあなた自身の責任で使用してください:)
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 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では機能しません。ここでスウィフト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 enumのSIMPLESTカウンターは以下を追加することです:
case Count
最終的には。そして...ビオラ-今あなたは数を持っています-速くてシンプル
0
、シーケンスにギャップがないことが必要です。
で動作するSwift 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では利用できなくなりました。
コードコマンダーからネイトクックスの回答へのコメントは引き続き有効です。
値をハードコーディングする必要がないので便利ですが、これにより、呼び出されるたびにすべての列挙値がインスタンス化されます。それはO(1)ではなくO(n)です。
静的な格納されたプロパティがジェネリック型でサポートされていないため、これをプロトコル拡張として使用する場合(ネイトクックのように各列挙型で実装しない場合)には、現在のところ回避策はありません。
とにかく、小さな列挙型の場合、これは問題になりません。典型的なユースケースは、あろうsection.count
ためUITableViews
、既にZorayrにより述べたように。
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
}
私は自分でこの問題を解決しました。プロトコル(EnumIntArray)とグローバルユーティリティ関数(enumIntArray)を作成して、「All」変数を列挙型に非常に簡単に追加できるようにしました(Swift 1.2を使用)。「all」変数には、列挙内のすべての要素の配列が含まれるため、カウントにall.countを使用できます
Int型の生の値を使用する列挙型でのみ機能しますが、おそらく他の型にインスピレーションを与えることができます。
また、上記や他の場所で読んだ「番号付けのギャップ」と「反復に時間がかかりすぎる」問題にも対処しています。
EnumIntArrayプロトコルを列挙型に追加してから、enumIntArray関数を呼び出して「すべて」の静的変数を定義し、最初の要素(および番号付けにギャップがある場合は最後の要素)を提供するという考え方です。
静的変数が初期化されるのは1回だけなので、すべての生の値を処理するオーバーヘッドはプログラムに1回しかヒットしません。
例(ギャップなし):
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
}
次のメソッドは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
列挙の最後の値に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()
}
}
}
これはマイナーですが、私はより良いO(1)ソリューションは、(次のようになると思いONLYあなたの列挙型がされた場合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
sに対してCOUNT
エラーをスローし、falseを返すenum値を検証する関数を追加できます。