Swift列挙型のカウントを取得するにはどうすればよいですか?


回答:


172

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'に準拠していません。」なぜ私にそれを強制するのですか?何か案が?
サティヤム2018年

@Satyam:DAFFlowとは何ですか?
マーティンR

申し訳ありませんが、「DAFFlow」は他のプロトコルから継承しない単純な列挙型であることを忘れていました
Satyam

1
これは最良の解決策ですが、明確にするために言えば、Apple開発者が実際にこれを使い始めることができるのは、Xcode 10(したがってSwift 4.2)がベータ版から出たとき(おそらく2018年9月14日頃)です。
JosephH 2018

1
@DaniSpringer:詳細はgithub.com/apple/swift-evolution/blob/master/proposals/…にあります。ただし、コンパイラの自動型推論により、通常は明示的にその型を必要としません。
Martin R

143

これについて詳しく説明しているブログ投稿がありますが、列挙型の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
    }()
}

16
値をハードコーディングする必要がないので便利ですが、これにより、呼び出されるたびにすべての列挙値がインスタンス化されます。これはO(1)ではなくO(n)です。:(
コードコマンダー、

4
これは、連続したIntに適したソリューションです。少し変更した方がいいです。静的カウントプロパティを静的メソッドcountCases()に変換し、それを静的定数caseCountに割り当てます。
トムペライア2015

2
@ShamsAhmed:計算された変数を静的な変数に変換しました。
ネイト・クック

3
列挙型の値を逃した場合はどうなりますか?例えばcase A=1, B=3
Sasho 2017

2
あなたが言及するのを忘れenumInt生の値を持っていることに加えて、2つの仮定があります:Intの生の値を持つSwift enumは0から始める必要はありません(それがデフォルトの動作ですが)、それらの生の値は任意にすることができますが、持っていません1ずつインクリメントします(デフォルトの動作ですが)。
デヴィッド・Pásztor

90

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を返します。

それがルールであるかどうか、または将来的に変更されるかどうかは言えないので、自己責任で使用してください :)


48
文書化されていない機能によって生きる、文書化されていない機能によって死ぬ。私はそれが好きです!
Nate Cook、

9
hashValuesこれらのことを実際に当てにするべきではありません。私たちが知っているのは、それがランダムな一意の値であることです。コンパイラの実装の詳細によっては、将来非常に簡単に変更される可能性があります。しかし全体として、組み込みのカウント機能の欠如は不安を引き起こします。
Zorayr、2015

16
あなたが明示的に設定を気にしない場合はcase ONE = 0、あなたがして置き換えることができhashValuerawValue
Kevin Qi

3
ここでの懸念は、hashValueのドキュメント化されていないプロパティの使用であるため、rawValueのドキュメント化されたプロパティを使用することをお勧めします。
Kevin Qi

7
あなたは既に定数が最高値、より良い、より安全なだけ利用するようなものになるの事実ハードコードしましたstatic var count = 4ではなく、スウィフトの将来の実装の運命にあなたの運命を残して
デール

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
表記はSwift 3で削除されるcount++ためcount+=1、次のように変更++すること
Aladin

1
同じことだけではできませんstatic var caseCount: Int { get }か?なぜ必要なのstatic funcですか?
pxpgraphics

列挙型の値を逃した場合はどうなりますか?例えばcase A=1, B=3
Sasho 2017

1
@Sasho、それでは機能しません。これは、あなたのケースが始まり、0ギャップがないことを必要とします。
NRitH 2017年

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、2017

2
次のようにして、列挙にカウントを追加することもできstatic let count = allValues.countます。次にallValues、必要に応じてプライベートにできます。
ThomasW 2018年

15

実装が整数の列挙型の使用に対して何もない場合は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!")
  }
}

enum値を追加するとカウントが自動的に変更されるため、このソリューションが気に入っています。しかし、列挙型のrawValuesが0で始まる場合にのみ動作することに注意してください
joern

2
同意します。2つの制限があります。整数の列挙型である必要があり、ゼロから始まり、増分的に継続する必要があります。
Zorayr 2015年

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
これはもはやSwift 3では機能しません。適切な実装を試してみますが、空になります
Cody Winton

終わりにすぐ隣接するメモリアドレスがあればこれは、デバッグに非常にunfunなりenum Hashable同じタイプで。
NRitH 2017年

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から、ビットキャストし、無効な場合指標、そのhashValueIS0

だからあなた自身の責任で使用してください:)

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、2015年

これはシミュレータでは機能しますが、実際の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

もちろん、それは動的ではありませんが、多くの用途では、静的変数を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では機能しません。ここでスウィフト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 enumのSIMPLESTカウンターは以下を追加することです:

case Count

最終的には。そして...ビオラ-今あなたは数を持っています-速くてシンプル


1
これは、a)無関係なenumケースを追加し、b)enumのrawタイプがInt以外の場合は機能しません。
ロバートアトキンス2017年

これは実際には悪い答えではありません。ただし、上記の@Tom Pelaiaの回答と同様に、生の値から開始し0、シーケンスにギャップがないことが必要です。
NRitH 2017年

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

で動作する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)はサポートされていません。これは、非常に断片化されたジェネリック型であり、多くの機能が不足しているためです。successorSwift 3では利用できなくなりました。

コードコマンダーからネイトクックスの回答へのコメントは引き続き有効です。

値をハードコーディングする必要がないので便利ですが、これにより、呼び出されるたびにすべての列挙値がインスタンス化されます。それはO(1)ではなくO(n)です。

静的な格納されたプロパティがジェネリック型でサポートされていないため、これをプロトコル拡張として使用する場合(ネイトクックのように各列挙型で実装しない場合)には、現在のところ回避策はありません。

とにかく、小さな列挙型の場合、これは問題になりません。典型的なユースケースは、あろうsection.countためUITableViews、既にZorayrにより述べたように。


1

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
}

0

私は自分でこの問題を解決しました。プロトコル(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   
}

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

列挙の最後の値に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)ソリューションは、(次のようになると思い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


3
enumのタイプを実際に表さない値をenumに追加すると、コードの悪臭がします。「ALL」や「NONE」を含めて正当化するのが難しいこともあります。この問題を回避するためだけに「COUNT」を含めるのは非常に臭いです。
マイケルピーターソン2016年

1
臭い?あなたがそれを確実に呼びたいなら。パフォーマンス?はい。長所と短所を決定するのは開発者の責任です。これは実際には上記のZorayrの回答に対する同じ回答であり、彼はそれについてより詳細に説明しており、新しく受け入れられた回答も同様です。しかし、swiftがAPIを追加するまでは、これは私たちの一部が使用することを決めたものです。型の表現に関する懸念を解決するために、guardsに対してCOUNTエラーをスローし、falseを返すenum値を検証する関数を追加できます。
Drmorgan 2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.