Question Comment énumérer une énumération avec un type String?


enum Suit: String {
    case spades = ""
    case hearts = ""
    case diamonds = ""
    case clubs = ""
}

Par exemple, comment puis-je faire quelque chose comme:

for suit in Suit {
    // do something with suit
    print(suit.rawValue)
}

Exemple résultant:






421
2018-06-03 05:03


origine


Réponses:


Swift 4.2+

Commençant par Swift 4.2 (avec Xcode 10), il suffit d'ajouter la conformité du protocole à CaseIterable bénéficier de allCases:

extension Suit: CaseIterable {}

Alors cela va imprimer toutes les valeurs possibles:

Suit.allCases.forEach {
    print($0.rawValue)
}

Compatibilité avec les versions précédentes de Swift (3.x et 4.x)

Juste imiter l'implémentation de Swift 4.2:

#if !swift(>=4.2)
public protocol CaseIterable {
    associatedtype AllCases: Collection where AllCases.Element == Self
    static var allCases: AllCases { get }
}
extension CaseIterable where Self: Hashable {
    static var allCases: [Self] {
        return [Self](AnySequence { () -> AnyIterator<Self> in
            var raw = 0
            var first: Self?
            return AnyIterator {
                let current = withUnsafeBytes(of: &raw) { $0.load(as: Self.self) }
                if raw == 0 {
                    first = current
                } else if current == first {
                    return nil
                }
                raw += 1
                return current
            }
        })
    }
}
#endif

59
2018-03-31 13:46



Ce post est pertinent ici https://www.swift-studies.com/blog/2014/6/10/enumerating-enums-in-swift

Essentiellement, la solution proposée est

enum ProductCategory : String {
     case Washers = "washers", Dryers = "dryers", Toasters = "toasters"

     static let allValues = [Washers, Dryers, Toasters]
}

for category in ProductCategory.allValues{
     //Do something
}

490
2018-06-10 09:21



J'ai fait une fonction utilitaire iterateEnum() pour les cas d'itération pour arbitraire enum les types.

Voici l'exemple d'utilisation:

enum Suit:String {
    case Spades = ""
    case Hearts = ""
    case Diamonds = ""
    case Clubs = ""
}

for f in iterateEnum(Suit) {
    println(f.rawValue)
}

les sorties:





Mais c'est seulement pour déboguer ou tester purpose: Cela repose sur plusieurs comportements de compilateurs actuels (Swift1.1) non documentés. Alors, utilisez-le à vos risques et périls :)

Voici le code:

func iterateEnum<T: Hashable>(_: T.Type) -> GeneratorOf<T> {
    var cast: (Int -> T)!
    switch sizeof(T) {
    case 0: return GeneratorOf(GeneratorOfOne(unsafeBitCast((), T.self)))
    case 1: cast = { unsafeBitCast(UInt8(truncatingBitPattern: $0), T.self) }
    case 2: cast = { unsafeBitCast(UInt16(truncatingBitPattern: $0), T.self) }
    case 4: cast = { unsafeBitCast(UInt32(truncatingBitPattern: $0), T.self) }
    case 8: cast = { unsafeBitCast(UInt64($0), T.self) }
    default: fatalError("cannot be here")
    }

    var i = 0
    return GeneratorOf {
        let next = cast(i)
        return next.hashValue == i++ ? next : nil
    }
}

L'idée sous-jacente est la suivante:

  • Représentation de la mémoire de enum - à l'exclusion enums avec les types associés - n'est qu'un index des cas, lorsque le décompte des cas est 2...256, c'est identique à UInt8, quand 257...65536, c'est UInt16 etc. Donc, ça peut être unsafeBitcast à partir des types entiers non signés correspondants.
  • .hashValue des valeurs enum est le même que l'index du cas.
  • .hashValue des valeurs enum bitcasted de invalide l'index est 0

AJOUTÉE:

Révisé pour Swift2 et implémenté des idées de casting de La réponse de Kametrixom

func iterateEnum<T: Hashable>(_: T.Type) -> AnyGenerator<T> {
    var i = 0
    return anyGenerator {
        let next = withUnsafePointer(&i) { UnsafePointer<T>($0).memory }
        return next.hashValue == i++ ? next : nil
    }
}

AJOUTÉE: Révisé pour Swift3

func iterateEnum<T: Hashable>(_: T.Type) -> AnyIterator<T> {
    var i = 0
    return AnyIterator {
        let next = withUnsafePointer(to: &i) {
            $0.withMemoryRebound(to: T.self, capacity: 1) { $0.pointee }
        }
        if next.hashValue != i { return nil }
        i += 1
        return next
    }
}

AJOUTÉE: Révisé pour Swift3.0.1

func iterateEnum<T: Hashable>(_: T.Type) -> AnyIterator<T> {
    var i = 0
    return AnyIterator {
        let next = withUnsafeBytes(of: &i) { $0.load(as: T.self) }
        if next.hashValue != i { return nil }
        i += 1
        return next
    }
}

272
2018-02-05 10:14



Les autres solutions travail mais ils font tous des suppositions par exemple sur le nombre de grades et de combinaisons possibles, ou sur ce que peuvent être le premier et le dernier rang. Certes, la disposition d'un jeu de cartes ne changera probablement pas beaucoup dans un avenir prévisible. En général, cependant, il est plus facile d'écrire du code qui fait aussi peu d'hypothèses que possible. Ma solution:

J'ai ajouté un type brut à l'enum de costume, donc je peux utiliser Suit (rawValue :) pour accéder aux cas Suit:

enum Suit: Int {
    case Spades = 1
    case Hearts, Diamonds, Clubs
    func simpleDescription() -> String {
        switch self {
            case .Spades:
                return "spades"
            case .Hearts:
                return "hearts"
            case .Diamonds:
                return "diamonds"
            case .Clubs:
                return "clubs"
        }
    }
    func color() -> String {
        switch self {
        case .Spades:
            return "black"
        case .Clubs:
            return "black"
        case .Diamonds:
            return "red"
        case .Hearts:
            return "red"
        }
    }
}

enum Rank: Int {
    case Ace = 1
    case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten
    case Jack, Queen, King
    func simpleDescription() -> String {
        switch self {
            case .Ace:
                return "ace"
            case .Jack:
                return "jack"
            case .Queen:
                return "queen"
            case .King:
                return "king"
            default:
                return String(self.rawValue)
        }
    }
}

Ci-dessous l'implémentation de la méthode createDeck () de Card. init (rawValue :) est un initialiseur fable et retourne un optionnel. En dépliant et en vérifiant sa valeur dans les deux instructions while, il n'est pas nécessaire de prendre en compte le nombre de cas Rank ou Suit:

struct Card {
    var rank: Rank
    var suit: Suit
    func simpleDescription() -> String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }
    func createDeck() -> [Card] {
        var n = 1
        var deck = [Card]()
        while let rank = Rank(rawValue: n) {
            var m = 1
            while let suit = Suit(rawValue: m) {
                deck.append(Card(rank: rank, suit: suit))
                m += 1
            }
            n += 1
        }
        return deck
    }
}

Voici comment appeler la méthode createDeck:

let card = Card(rank: Rank.Ace, suit: Suit.Clubs)
let deck = card.createDeck()

126
2018-06-14 16:46



La deuxième réponse qui fonctionne vraiment

Donc, je suis tombé sur les bits et les octets et créé une extension (que j'ai découvert plus tard des travaux très similaires à @rintarola réponse). C'est utilisable comme ceci:

enum E : EnumCollection {
    case A, B, C
}

Array(E.cases())    // [A, B, C]

Ce qui est remarquable, c'est qu'il est utilisable sur n'importe quel enum (sans valeurs associées). Notez que cela ne fonctionne pas pour les énumérations qui n'ont pas de cas.

Avertissement

Comme avec @rintaroréponse, ce code utilise la représentation sous-jacente d'un enum. Cette représentation n'est pas documentée et pourrait changer dans le futur, ce qui la briserait -> Je ne recommande pas l'utilisation de ceci dans la production.

EDIT: Cela fait environ un an et ça marche toujours.

Code (Swift 2.2, Xcode 7.3.1)

protocol EnumCollection : Hashable {}
extension EnumCollection {
    static func cases() -> AnySequence<Self> {
        typealias S = Self
        return AnySequence { () -> AnyGenerator<S> in
            var raw = 0
            return AnyGenerator {
                let current : Self = withUnsafePointer(&raw) { UnsafePointer($0).memory }
                guard current.hashValue == raw else { return nil }
                raw += 1
                return current
            }
        }
    }
}

Code (Swift 3, Xcode 8.1)

protocol EnumCollection : Hashable {}
extension EnumCollection {
    static func cases() -> AnySequence<Self> {
        typealias S = Self
        return AnySequence { () -> AnyIterator<S> in
            var raw = 0
            return AnyIterator {
                let current : Self = withUnsafePointer(to: &raw) { $0.withMemoryRebound(to: S.self, capacity: 1) { $0.pointee } }
                guard current.hashValue == raw else { return nil }
                raw += 1
                return current
            }
        }
    }
}

(Je ne sais pas pourquoi j'ai besoin de typealias, mais le compilateur se plaint sans)

(J'ai fait une grande modification à cette réponse, regardez les modifications pour les versions antérieures)


71
2017-09-06 23:04



Vous pouvez parcourir un enum en mettant en œuvre le ForwardIndexType protocole.

le ForwardIndexType protocole vous oblige à définir un successor() fonctionner pour parcourir les éléments.

enum Rank: Int, ForwardIndexType {
    case Ace = 1
    case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten
    case Jack, Queen, King

    // ... other functions

    // Option 1 - Figure it out by hand
    func successor() -> Rank {
        switch self {
            case .Ace:
              return .Two
            case .Two:
              return .Three

            // ... etc.

            default:
              return .King
        }
    }

    // Option 2 - Define an operator!
    func successor() -> Rank {
        return self + 1
    }
}

// NOTE: The operator is defined OUTSIDE the class
func + (left: Rank, right: Int) -> Rank {
    // I'm using to/from raw here, but again, you can use a case statement
    // or whatever else you can think of

    return left == .King ? .King : Rank(rawValue: left.rawValue + right)!
}

Itérer sur une plage ouverte ou fermée (..< ou ...) appellera en interne le successor() fonction qui vous permet d'écrire ceci:

// Under the covers, successor(Rank.King) and successor(Rank.Ace) are called to establish limits
for r in Rank.Ace...Rank.King {
    // Do something useful
}

25
2017-07-16 22:27



En principe, il est possible de le faire de cette façon en supposant que vous n'utilisez pas l'affectation de valeurs brutes pour les cas d'enum:

enum RankEnum: Int {
  case Ace
  case One
  case Two
}

class RankEnumGenerator : Generator {
  var i = 0
  typealias Element = RankEnum
  func next() -> Element? {
    let r = RankEnum.fromRaw(i)
    i += 1
    return r
  }
}

extension RankEnum {
  static func enumerate() -> SequenceOf<RankEnum> {
    return SequenceOf<RankEnum>({ RankEnumGenerator() })
  }
}

for r in RankEnum.enumerate() {
  println("\(r.toRaw())")
}

17
2018-06-03 22:02



Mis à jour à Swift 2.2+

func iterateEnum<T: Hashable>(_: T.Type) -> AnyGenerator<T> {
    var i = 0
    return AnyGenerator {
        let next = withUnsafePointer(&i) {
            UnsafePointer<T>($0).memory
        }
        if next.hashValue == i {
            i += 1
            return next
        } else {
            return nil
        }
    }
}

c'est le code mis à jour au formulaire Swift 2.2 @ Kametrixom est unswer

Pour Swift 3.0+ (merci beaucoup à @Philippe)

func iterateEnum<T: Hashable>(_: T.Type) -> AnyIterator<T> {
    var i = 0
    return AnyIterator {
        let next = withUnsafePointer(&i) {
            UnsafePointer<T>($0).pointee
        }
        if next.hashValue == i {
            i += 1
            return next
        } else {
            return nil
        }
    }
}

13
2018-03-23 12:08



Si vous donnez l'enum une valeur brute cela rendra la boucle beaucoup plus facile.

Par exemple, vous pouvez utiliser anyGenerator pour obtenir un générateur pouvant énumérer vos valeurs:

enum Suit: Int, CustomStringConvertible {
    case Spades, Hearts, Diamonds, Clubs
    var description: String {
        switch self {
        case .Spades:   return "Spades"
        case .Hearts:   return "Hearts"
        case .Diamonds: return "Diamonds"
        case .Clubs:    return "Clubs"
        }
    }
    static func enumerate() -> AnyGenerator<Suit> {
        var nextIndex = Spades.rawValue
        return anyGenerator { Suit(rawValue: nextIndex++) }
    }
}
// You can now use it like this:
for suit in Suit.enumerate() {
    suit.description
}
// or like this:
let allSuits: [Suit] = Array(Suit.enumerate())

Cependant, cela ressemble à un modèle assez commun, ne serait-il pas intéressant de pouvoir énumérer un type enum en se conformant simplement à un protocole? Eh bien avec Swift 2.0 et les extensions de protocole, maintenant nous pouvons!

Ajoutez simplement ceci à votre projet:

protocol EnumerableEnum {
    init?(rawValue: Int)
    static func firstValue() -> Int
}
extension EnumerableEnum {
    static func enumerate() -> AnyGenerator<Self> {
        var nextIndex = firstRawValue()
        return anyGenerator { Self(rawValue: nextIndex++) }
    }
    static func firstRawValue() -> Int { return 0 }
}

Maintenant, chaque fois que vous créez une énumération (tant qu'elle a une valeur brute Int), vous pouvez la rendre énumérable en se conformant au protocole:

enum Rank: Int, EnumerableEnum {
    case Ace, Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten, Jack, Queen, King
}
// ...
for rank in Rank.enumerate() { ... }

Si vos valeurs enum ne commencent pas par 0 (par défaut), remplacez le firstRawValue méthode:

enum DeckColor: Int, EnumerableEnum {
    case Red = 10, Blue, Black
    static func firstRawValue() -> Int { return Red.rawValue }
}
// ...
let colors = Array(DeckColor.enumerate())

La dernière classe Suit, y compris le remplacement simpleDescription avec le protocole standard plus CustomStringConvertible, ressemblera à ceci:

enum Suit: Int, CustomStringConvertible, EnumerableEnum {
    case Spades, Hearts, Diamonds, Clubs
    var description: String {
        switch self {
        case .Spades:   return "Spades"
        case .Hearts:   return "Hearts"
        case .Diamonds: return "Diamonds"
        case .Clubs:    return "Clubs"
        }
    }
}
// ...
for suit in Suit.enumerate() {
    print(suit.description)
}

MODIFIER:

Swift 3 syntaxe:

protocol EnumerableEnum {
    init?(rawValue: Int)
    static func firstRawValue() -> Int
}

extension EnumerableEnum {
    static func enumerate() -> AnyIterator<Self> {
        var nextIndex = firstRawValue()

        let iterator: AnyIterator<Self> = AnyIterator {
            defer { nextIndex = nextIndex + 1 }
            return Self(rawValue: nextIndex)
        }

        return iterator
    }

    static func firstRawValue() -> Int {
        return 0
    }
}

13
2017-09-05 23:08



J'aime cette solution que j'ai mise en place après avoir trouvé cette page: La compréhension de la liste dans Swift 

Il utilise Int raws au lieu de Strings mais il évite de taper deux fois, il permet de personnaliser les gammes et ne code pas dur les valeurs brutes.

enum Suit: Int {
    case None
    case Spade, Heart, Diamond, Club

    static let allRawValues = Suit.Spade.rawValue...Suit.Club.rawValue
    static let allCases = Array(allRawValues.map{ Suit(rawValue: $0)! })
}

enum Rank: Int {
    case Joker
    case Two, Three, Four, Five, Six
    case Seven, Eight, Nine, Ten
    case Jack, Queen, King, Ace

    static let allRawValues = Rank.Two.rawValue...Rank.Ace.rawValue
    static let allCases = Array(allRawValues.map{ Rank(rawValue: $0)! })
}

func makeDeck(withJoker withJoker: Bool) -> [Card] {
    var deck = [Card]()
    for suit in Suit.allCases {
        for rank in Rank.allCases {
            deck.append(Card(suit: suit, rank: rank))
        }
    }
    if withJoker {
        deck.append(Card(suit: .None, rank: .Joker))
    }
    return deck
}

9
2017-12-06 12:03



Je me suis retrouvé à faire .allValues beaucoup dans mon code. J'ai finalement trouvé un moyen de se conformer simplement à un Iteratable protocole et avoir un rawValues() méthode.

protocol Iteratable {}
extension RawRepresentable where Self: RawRepresentable {

    static func iterateEnum<T: Hashable>(_: T.Type) -> AnyIterator<T> {
        var i = 0
        return AnyIterator {
            let next = withUnsafePointer(to: &i) {
                $0.withMemoryRebound(to: T.self, capacity: 1) { $0.pointee }
            }
            if next.hashValue != i { return nil }
            i += 1
            return next
        }
    }
}

extension Iteratable where Self: RawRepresentable, Self: Hashable {
    static func hashValues() -> AnyIterator<Self> {
        return iterateEnum(self)
    }

    static func rawValues() -> [Self.RawValue] {
        return hashValues().map({$0.rawValue})
    }
}


// Example
enum Grocery: String, Iteratable {
    case Kroger = "kroger"
    case HEB = "h.e.b."
    case Randalls = "randalls"
}

let groceryHashes = Grocery.hashValues() // AnyIterator<Grocery>
let groceryRawValues = Grocery.rawValues() // ["kroger", "h.e.b.", "randalls"]

7
2017-11-01 18:48