Comment énumérer une énumération de type chaîne?

530
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:

♠
♥
♦
♣
Lucien
la source
Dans quel cas ne connaîtriez-vous pas le type?
mtbennett
Vous avez raison, dans ce cas c'est du type String.
Lucien
Pas encore de réflexion dans Swift ...
Sulthan
22
N'est-il pas ironique qu'on les appelle des énumérations, mais elles sont si péniblement ennuyeuses à énumérer dans Swift?
Charlton Provatas
3
@CharltonProvatas Si c'était le seul inconvénient de Swift, je l'appellerais un jour. En regardant combien de personnes proposent différentes solutions pour cela, je ronge juste mon clavier.
qwerty_so

Réponses:

267

Swift 4.2+

À partir de Swift 4.2 (avec Xcode 10), ajoutez simplement la conformité du protocole pour CaseIterableen bénéficier allCases. Pour ajouter cette conformité au protocole, il vous suffit d'écrire quelque part:

extension Suit: CaseIterable {}

Si l'énumération est la vôtre, vous pouvez spécifier la conformité directement dans la déclaration:

enum Suit: String, CaseIterable { case spades = "♠"; case hearts = "♥"; case diamonds = "♦"; case clubs = "♣" }

Ensuite, le code suivant imprimera toutes les valeurs possibles:

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

Compatibilité avec les versions antérieures de Swift (3.x et 4.x)

Si vous devez prendre en charge Swift 3.x ou 4.0, vous pouvez imiter l'implémentation de Swift 4.2 en ajoutant le code suivant:

#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
Cœur
la source
@DmitryPetukhov Je serais heureux de vous aider, mais: (1) êtes-vous sûr d'avoir la dernière version du code? (un crash a été corrigé il y a un mois) et (2) veuillez donner un MCVE de votre type personnalisé qui peut reproduire un crash, et votre version de Xcode.
Cœur
Cela fonctionne bien pour moi pour les versions de débogage, mais dès que je crée une version et que je la télécharge sur TestFlight, elle se bloque. Apple supprime-t-il cela d'une manière ou d'une autre?
Daniel Wood
1
Il semble que votre version ait un point positif par rapport à la version intégrée de CaseIterator. Je peux étendre les énumérations définies dans un autre fichier avec votre version. Si vous rendez public tous les cas dans l'extension, vous pouvez également étendre les énumérations définies dans différents cadres.
adamfowlerphoto
1
@CyberMew J'ai mis à jour la réponse pour la clarifier. Le livre Swift et les notes de publication de Xcode 10 mentionnant CaseIterable sont postérieurs à ma réponse et ils ont utilisé des exemples simplifiés où l'énumération n'est pas sauvegardée String, par opposition à la question actuelle Stack Overflow.
Cœur
1
Je voudrais souligner l'importance du "# if! Swift (> = 4.2)". Si vous avez écrit votre code avant swift 4.2 et que vous avez oublié de supprimer le code ci-dessous "# if! Swift (> = 4.2)", lorsque vous utilisez Xcode version 11.4 pour construire localement sur votre appareil de test, tout ira bien. Mais lorsque votre application est téléchargée sur l'App Store ou en vol d'essai, ce morceau de code plantera votre application. Ce type de bogue est très difficile à repérer ou à déboguer.
Oliver Zhang
524

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

La solution proposée est essentiellement

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

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

for category in ProductCategory.allValues{
     //Do something
}
rougeExciter
la source
188
Bien, mais ... vous devez entrer vos éléments de l'énumération deux fois - une fois pour l'énumération, une fois pour toutes les valeurs. Pas exactement aussi élégant qu'on le souhaiterait.
Jay Imerman
2
D'accord avec le "mais" ... cependant, comme indiqué dans l'article, il y a peut-être un problème selon lequel une énumération est vraiment un ensemble et donc non ordonnée ... attention ... les cas de commande définis dans ne seraient pas un mauvais début!
rougeExciter
3
En Java, le compilateur fait cela pour vous, peut-être que Swift 2.0 le fera également. En particulier en Java, toutes les énumérations obtiennent une méthode de description (toString en Java) qui donne la chaîne comme noms de cas (rondelles, ...) et un ensemble de cas est automatiquement créé. Java vous offre également une indexation positionnelle. Comme je l'ai dit, peut-être Swift 2.0.
Howard Lovatt
1
Idéalement, vous auriez quelque chose de similaire à l'implémentation c # dans lequel vous pouvez le faire Enum.Values(typeof(FooEnum))mais exposé comme une méthode d'extension (comme mapper ou réduire). FooEnum.values() :: values(EnumType -> [EnumType])
rodrigoelp
1
L'article fait un bon point à la fin sur la création de chaque valeur d'énumération dans le tableau allValues ​​avec un test unitaire. Cependant, je peux toujours voir quelqu'un ajouter plus d'éléments, mais ne pas les appliquer dans le test unitaire qui nous laisse toujours au début, sans savoir avec certitude que toutes les valeurs enum sont conservées dans allValues.
DonnaLea
278

J'ai fait une fonction utilitaire iterateEnum()pour itérer les cas pour les enumtypes arbitraires .

Voici l'exemple d'utilisation:

enum Suit: String {
    case Spades = "♠"
    case Hearts = "♥"
    case Diamonds = "♦"
    case Clubs = "♣"
}

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

Quelles sorties:

♠
♥
♦
♣

Mais, c'est uniquement à des fins de débogage ou de test : cela repose sur plusieurs comportements de compilateur 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 représentation en mémoire de enum, à l'exclusion de enums avec les types associés, n'est qu'un index des cas lorsque le nombre de cas est 2...256, il est identique à UInt8, quand 257...65536, c'est UInt16et ainsi de suite. Il peut donc provenir unsafeBitcastde types entiers non signés correspondants.
  • .hashValue des valeurs d'énumération est identique à l'indice du cas.
  • .hashValuedes valeurs d'énumération diffusées à partir d'un index non valide est 0.

Révisé pour Swift2 et mis en œuvre des idées de casting à partir 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
    }
}

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
    }
}

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
    }
}
rintaro
la source
20
Génial et la seule réponse qui répond à la question! Mais ouais ... je ne vais pas y toucher! +1 pour l'effort, cependant!
dotToString
Je viens de poster ma réponse qui fonctionne essentiellement de la même manière (seulement vu cette réponse plus tard). Il utilise Swift 2.0 beta 6 et les fonctionnalités modernes du langage.
Kametrixom
2
La version Swift 3 fonctionne bien. Juste besoin de modifier un peu l'utilisation: pour f dans iterateEnum (Suit.self) {print (f.rawValue)}
Gene De Lisa
16
+1 c'est assez brillant. Il est également, à mon humble avis, trop intelligent à utiliser, comme en témoigne sa rupture significative dans chaque changement majeur de version Swift. Au crédit de l'auteur, la version Swift 3 a été réalisée un mois avant la sortie de la version bêta de Swift 3 ... Si vous voulez prendre cette réponse et apprendre tout cela withUnsafePointer withMemoryReboundet d'autres pointeechoses, utilisez-la par tous les moyens. Sinon, je l'éviterais.
Dan Rosenstark
5
Je veux juste ajouter que c'est maintenant cassé dans swift 4, mais seulement sur linux, donc +1 aux commentaires ci-dessus que c'est trop intelligent à utiliser.
Andrew Plummer
130

Les autres solutions fonctionnent mais elles font toutes des hypothèses, par exemple sur le nombre de rangs 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 pratique d'écrire du code qui fait le moins d'hypothèses possible. Ma solution:

J'ai ajouté un type brut à l' Suiténumération, donc je peux utiliser Suit(rawValue:)pour accéder aux Suitcas:

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 createDeck()méthode Card . init(rawValue:)est un initialiseur disponible et renvoie une option. En déballant et en vérifiant sa valeur dans les deux instructions while, il n'est pas nécessaire de supposer le nombre Rankou les Suitcas:

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 createDeckméthode:

let card = Card(rank: Rank.Ace, suit: Suit.Clubs)
let deck = card.createDeck()
sdduursma
la source
12
Meilleure réponse absolue que j'ai vue sur divers sujets sur ce sujet. Très élégant. Cela fonctionne avec les énumérations de type Int, mais je me demande comment on peut parcourir les autres types (chaîne, types personnalisés, etc.).
Jay Imerman
3
C'est certainement la meilleure solution. Une chose à noter. Dans l'exemple du livre, il n'a pas "case Spades = 1" comme sdduursma. Je n'ai pas compris ça au début. c'est une option, ou vous pouvez simplement utiliser "var m = 0"
Joshua Goossen
10
Cela fait l'hypothèse que les valeurs brutes sont séquentielles. Lorsque ce n'est pas le cas, par exemple lorsque l'énumération représente des indicateurs de masque de bits, la boucle se ferme prématurément.
Jim
1
Cette solution suppose que vous pouvez modifier la définition de Suit. Vous pouvez le faire dans cet exemple, mais l'exercice était destiné à vous amener à travailler avec les données qui enumsvous ont été données comme si elles provenaient d'une source externe.
Josh J
1
La seule chose dont je veux me plaindre, c'est que je ne peux pas l'appeler comme une méthode statique, mais je dois d'abord créer un objet carte.
qed
76

Je suis tombé sur les bits et les octets et j'ai créé une extension dont j'ai découvert plus tard que les travaux étaient très similaires à la réponse de @rintaro . Il est utilisé 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 quelle énumération sans valeurs associées. Notez que cela ne fonctionne pas pour les énumérations qui n'ont pas de cas.

Comme pour la réponse de @rintaro , ce code utilise la représentation sous-jacente d'une énumération. Cette représentation n'est pas documentée et pourrait changer à l'avenir, ce qui la briserait. Je ne recommande pas l'utilisation de ceci en production.

Code (Swift 2.2, Xcode 7.3.1, ne fonctionne pas sur Xcode 10):

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, ne fonctionne pas sur Xcode 10):

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 n'ai aucune idée pourquoi j'ai besoin typealias, mais le compilateur se plaint sans cela.

Kametrixom
la source
10
Cette réponse est encore meilleure que ma réponse, en particulier dans la partie casting :)
rintaro
Mais je pense que cela ne fonctionne que sur un petit environnement endian?
rintaro
5
Xcode 8 beta 6 a encore changé cela! J'obtiens l'erreur suivante `` init '' n'est pas disponible: utilisez 'withMemoryRebound (to: capacity: _)' pour afficher temporairement la mémoire comme un autre type compatible avec la mise en page. '
Confused Vorlon
1
@ConfusedVorlon: voir la réponse ci-dessus par @Rintaro: replace withUnsafePointerpointee}bywithUnsafePointer(to: &i) { $0.withMemoryRebound(to: T.self, capacity: 1) { $0.pointee } }
Stefan
1
Cela semble ne plus fonctionner à partir de Xcode 10. (Je sais que cela ne serait pas requis avec Swift 4.2) mais lorsque vous utilisez Swift 4 (.1) dans Xcode 10, ce code ne fonctionne plus (la valeur brute est inégale)
benrudhart
26

Vous pouvez parcourir une énumération en implémentant le ForwardIndexTypeprotocole.

le ForwardIndexType protocole vous oblige à définir une successor()fonction 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 la 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
}
RndmTsk
la source
2
Je trouve que c'est la réponse la plus "correcte" à la question, même la plus "élégante" (un code supplémentaire est nécessaire vis-à-vis des autres options ici) étant donné la syntaxe résultante lorsqu'elle est utilisée dans une plage (la syntaxe est ce que je s'attendrait à pouvoir faire si des énumérations où des énumérations sans contournement). Merci! Bien qu'il soit intéressant de noter que si la surcharge de l'opérateur est utilisée ailleurs que successor () (ce qui semble tentant), le déballage forcé est évidemment dangereux. De plus, l'infixe semble inutile ...?
iOS Gamer
Réponse mise à jour pour refléter les dernières spécifications du langage Swift
RndmTsk
Une successor()méthode correctement définie (première option) éliminerait la nécessité enumd'avoir un type associé. +1
nhgrif
1
Mais cette réponse élégante ne fonctionnera pas pour les énumérations de chaînes, n'est-ce pas?
Ali
La solution la plus "appropriée" / meilleure pratique! + 1-ed
Siu Ching Pong -Asuka Kenji-
18

Ce problème est maintenant beaucoup plus facile. Voici ma solution Swift 4.2:

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

  static let allNonNullCases = Suit.allCases[Spade.rawValue...]
}

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

  static let allNonNullCases = Rank.allCases[Two.rawValue...]
}

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

Pré 4.2:

J'aime cette solution que j'ai mise en place après avoir trouvé " Compréhension de liste dans Swift ".

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

Il s'agit d'une version Swift 4 de ma solution d'origine, mais voyez l'amélioration 4.2 ci-dessus:

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: Bool = false) -> [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
}
adazacom
la source
Oh, je vois maintenant que la mienne est fondamentalement la même que celle de Sutean Rutjanalard.
adazacom
1
En fait, j'ai mieux aimé votre implémentation. Je pense que c'est plus clair! 1 vote positif. En fait, les réponses les plus votées sont trop intelligentes et se casseront très certainement à l'avenir. Le vôtre promet une certaine stabilité à l'avenir.
jvarela
17

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'énumération:

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())")
}
Alfa07
la source
7
C'est bien, mais cela ne fonctionne que pour les énumérations entières continues à partir de 0
Robert
@Robert, comme mon commentaire l'indique ci-dessus: "vous n'utilisez pas l'affectation de valeurs brutes pour les cas d'énumération"
Alfa07
Oui - n'utilisez pas de valeurs brutes et définissez le type sous-jacent sur int. Dans swift, vous n'avez pas besoin d'un type pour une énumération comme dans l'exemple des combinaisons. enum ItWontWorkForThisEnum {case a, b, c}
Robert
Comment cela résoudrait-il le problème si un tuple était associé au cas d'énumération?
rougeExciter
Vous ne pouvez pas associer un tuple à une énumération très facilement.
nhgrif
13

Si vous donnez à l'énumération une valeur Int brute, cela rendra la boucle beaucoup plus facile.

Par exemple, vous pouvez utiliser anyGeneratorpour obtenir un générateur qui peut é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 courant, ne serait-ce pas bien si nous pouvions rendre n'importe quel type d'énumération énumérable en se conformant simplement à un protocole? Eh bien, avec Swift 2.0 et les extensions de protocole, nous le pouvons maintenant!

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 vous 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 d'énumération ne commencent pas par 0(la valeur par défaut), remplacez la firstRawValuemé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 simpleDescriptionpar le protocole CustomStringConvertible plus standard , 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)
}

Syntaxe Swift 3:

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
    }
}
Sensé
la source
nextIndex ++ sera supprimé dans swift 3. Que proposez-vous comme remplacement de - var nextIndex = firstRawValue () return anyGenerator {Self (rawValue: nextIndex ++)}
Avi
Deviner. defer {nextIndex + = 1} return AnyGenerator {Self (rawValue: nextIndex)}
Avi
12

Mise à jour vers 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 un code mis à jour au format Swift 2.2 @ réponse de Kametrixom

Pour Swift 3.0+ (merci à @Philip )

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
        }
    }
}
ale_stro
la source
@silvansky pourriez-vous expliquer ce que vous voulez dire?
ale_stro
Oups, désolé, j'ai revérifié et il y avait un bug dans la cour de récréation: dans le projet réel, ce code fonctionne comme prévu, merci! =)
silvansky
1
Excellente solution! Fonctionne également très bien sur Swift 3 avec des modifications minimes («AnyGenerator» renommé «AnyIterator» et «.memory» renommé en «.pointee»).
Philip
9

Solution Swift 5:

enum Suit: String, CaseIterable {
    case spades = "♠"
    case hearts = "♥"
    case diamonds = "♦"
    case clubs = "♣"
}

// access cases like this:

for suitKey in Suit.allCases {
    print(suitKey)
}
JD Wooder
la source
7

Je me suis retrouvé à faire .allValuesbeaucoup tout au long de mon code. J'ai finalement trouvé un moyen de simplement me conformer à un Iteratableprotocole et d'avoir une 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"]
Fabian Buentello
la source
7

Xcode 10 avec Swift 4.2

enum Filter: String, CaseIterable {

    case salary = "Salary"
    case experience = "Experience"
    case technology = "Technology"
    case unutilized = "Unutilized"
    case unutilizedHV = "Unutilized High Value"

    static let allValues = Filter.allCases.map { $0.rawValue }
}

Appeler

print(Filter.allValues)

Tirages:

["Salaire", "Expérience", "Technologie", "Non utilisé", "Valeur élevée non utilisée"]


Versions plus anciennes

Pour enumreprésenterInt

enum Filter: Int {
    case salary
    case experience
    case technology
    case unutilized
    case unutilizedHV

    static let allRawValues = salary.rawValue...unutilizedHV.rawValue  // First to last case
    static let allValues = allRawValues.map { Filter(rawValue: $0)!.rawValue }
}

Appelez ça comme ceci:

print(Filter.allValues)

Tirages:

[0, 1, 2, 3, 4]


Pour enumreprésenterString

enum Filter: Int {
    case salary
    case experience
    case technology
    case unutilized
    case unutilizedHV

    static let allRawValues = salary.rawValue...unutilizedHV.rawValue  // First to last case
    static let allValues = allRawValues.map { Filter(rawValue: $0)!.description }
}

extension Filter: CustomStringConvertible {
    var description: String {
        switch self {
        case .salary: return "Salary"
        case .experience: return "Experience"
        case .technology: return "Technology"
        case .unutilized: return "Unutilized"
        case .unutilizedHV: return "Unutilized High Value"
        }
    }
}

Appeler

print(Filter.allValues)

Tirages:

["Salaire", "Expérience", "Technologie", "Non utilisé", "Valeur élevée non utilisée"]

Warif Akhand Rishi
la source
7

EDIT: Proposition Swift Evolution SE-0194 La collection dérivée de cas d'énumération propose une solution de niveau à ce problème. Nous le voyons dans Swift 4.2 et plus récent. La proposition souligne également certaines solutions de contournement qui sont similaires à certaines déjà mentionnées ici, mais il pourrait être intéressant de voir néanmoins.

Je conserverai également mon message d'origine par souci d'exhaustivité.


Il s'agit d'une autre approche basée sur la réponse de @ Peymmankh, adaptée à Swift 3 .

public protocol EnumCollection: Hashable {}

extension EnumCollection {

public static func allValues() -> [Self] {
    typealias S = Self

    let retVal = AnySequence { () -> AnyIterator<S> in
        var raw = 0
        return AnyIterator {
            let current = withUnsafePointer(to: &raw) {
                 $0.withMemoryRebound(to: S.self, capacity: 1) { $0.pointee }
            }
            guard current.hashValue == raw else { return nil }
            raw += 1
            return current
        }
    }

    return [S](retVal)
}
ff10
la source
5
enum Rank: Int {
    ...
    static let ranks = (Rank.Ace.rawValue ... Rank.King.rawValue).map{Rank(rawValue: $0)! }

}
enum Suit {
    ...
    static let suits = [Spades, Hearts, Diamonds, Clubs]
}

struct Card {
    ...
    static func fullDesk() -> [Card] {
        var desk: [Card] = []
        for suit in Suit.suits {
            for rank in Rank.ranks {
                desk.append(Card(rank: rank,suit: suit))
            }
        }
        return desk
    }
}

Que dis-tu de ça?

Mossila
la source
Merci, cela fonctionne comme j'ai besoin. Mais y a-t-il une possibilité dans la fermeture de carte d'obtenir la valeur non pas par index mais par nom?
Mikhail
4

Vous pouvez essayer d'énumérer comme ceci

enum Planet: String {
    case Mercury
    case Venus
    case Earth
    case Mars

    static var enumerate: [Planet] {
        var a: [Planet] = []
        switch Planet.Mercury {
            case .Mercury: a.append(.Mercury); fallthrough
            case .Venus: a.append(.Venus); fallthrough
            case .Earth: a.append(.Earth); fallthrough
            case .Mars: a.append(.Mars)
        }
    return a
    }
}

Planet.enumerate // [Mercury, Venus, Earth, Mars]
Karthik Kumar
la source
1
C'est beaucoup de code inutile! C'est équivalent à static var enumerate = [Mercury, Venus, Earth, Mars], ce qui en fait une réponse médiocre par rapport à la réponse la plus votée stackoverflow.com/a/24137319/1033581
Cœur
@ Cœur cette réponse a l'avantage important d'utiliser le compilateur pour garantir que vous ne manquerez pas un cas.
dchakarov
@ Cœur qui a le même problème de vous permettre de faire une erreur utilisateur, c'est-à-dire que le compilateur ne se plaindra pas si vous écrivez à la return [Mercury, Venus, Mars]place dereturn [Mercury, Venus, Earth, Mars]
dchakarov
@dchakarov J'ai décidé de poster l'amélioration comme réponse, pour plus de clarté: stackoverflow.com/a/50409525/1033581
Cœur
@ Cœur Si dans votre nouvelle réponse vous remplacez la déclaration de retour par celle-ci, return [.spades, .hearts, .clubs]le compilateur ne dira rien et puis lorsque vous essayez de l'utiliser dans du code, vous obtiendrez [TestApp.Suit.spades, TestApp.Suit.hearts, TestApp.Suit.clubs]- c'était mon point de vue - que si vous traitez avec un grand Enum et vous devez ajouter ou supprimer des cas de temps en temps, votre solution est sujette à des erreurs d'omission tandis que la réponse actuelle, bien que non concise, est plus sûre.
dchakarov
4

Dans Swift 3, lorsque l'énumération sous-jacente a rawValue, vous pouvez implémenter le Strideableprotocole. Les avantages sont qu'aucun tableau de valeurs n'est créé comme dans certaines autres suggestions et que la boucle Swift "for in" standard fonctionne, ce qui fait une belle syntaxe.

// "Int" to get rawValue, and Strideable so we can iterate
enum MyColorEnum: Int, Strideable {
    case Red
    case Green
    case Blue
    case Black

    // required by Strideable
    typealias Stride = Int

    func advanced(by n:Stride) -> MyColorEnum {
        var next = self.rawValue + n
        if next > MyColorEnum.Black.rawValue {
            next = MyColorEnum.Black.rawValue
        }
        return MyColorEnum(rawValue: next)!
    }

    func distance(to other: MyColorEnum) -> Int {
        return other.rawValue - self.rawValue
    }

    // just for printing
    func simpleDescription() -> String {
        switch self {
        case .Red: return "Red"
        case .Green: return "Green"
        case .Blue: return "Blue"
        case .Black: return "Black"
        }
    }
}

// this is how you use it:
for i in MyColorEnum.Red ... MyColorEnum.Black {
    print("ENUM: \(i)")
}
ACK
la source
Ahh, juste ce que je cherchais pour remplacer ForwardIndexType. Maintenant, mes itérations semblent bonnes sur le site d'utilisation ... juste la bonne façon Swifty.
Andrew Duncan
4

Cette solution trouve le juste équilibre entre lisibilité et maintenabilité.

struct Card {

    // ...

    static func deck() -> Card[] {
        var deck = Card[]()
        for rank in Rank.Ace.toRaw()...Rank.King.toRaw() {
            for suit in [Suit.Spades, .Hearts, .Clubs, .Diamonds] {
                let card = Card(rank: Rank.fromRaw(rank)!, suit: suit)
                deck.append(card)
            }
        }
    return deck
    }
}

let deck = Card.deck()
Andrew
la source
À mon avis, c'est la meilleure solution. Quand je vois du code rapide, la lisibilité n'est pas meilleure que objc. Mais cela pourrait l'être, si les programmeurs accordaient une plus grande attention aux lecteurs de leur code. Leur futur, par exemple :)
Vilém Kurz
4

Désolé, ma réponse était spécifique à la façon dont j'ai utilisé ce message dans ce que je devais faire. Pour ceux qui tombent sur cette question, à la recherche d'un moyen de trouver un cas dans une énumération, voici la façon de le faire (nouveau dans Swift 2):

Edit: minuscules camelCase est maintenant la norme pour les valeurs d'énumération Swift 3

// From apple docs: If the raw-value type is specified as String and you don’t assign values to the cases explicitly, each unassigned case is implicitly assigned a string with the same text as the name of that case.

enum Theme: String
    {
    case white, blue, green, lavender, grey
    }

func loadTheme(theme: String)
    {
    // this checks the string against the raw value of each enum case (note that the check could result in a nil value, since it's an optional, which is why we introduce the if/let block
    if let testTheme = Theme(rawValue: theme)
        {
        // testTheme is guaranteed to have an enum value at this point
        self.someOtherFunction(testTheme)
        }
    }

Pour ceux qui s'interrogent sur l'énumération sur une énumération, les réponses données sur cette page qui incluent un var / let statique contenant un tableau de toutes les valeurs de l'énumération sont correctes. Le dernier exemple de code Apple pour tvOS contient exactement la même technique.

Cela étant dit, ils devraient construire un mécanisme plus pratique dans la langue (Apple, écoutez-vous?)!

Gene Loparco
la source
3

L'expérience était: EXPÉRIENCE

Ajoutez une méthode à la carte qui crée un jeu complet de cartes, avec une carte de chaque combinaison de rang et de couleur.

Donc, sans modifier ou améliorer le code donné autre que l'ajout de la méthode (et sans utiliser des choses qui n'ont pas encore été enseignées), j'ai trouvé cette solution:

struct Card {
    var rank: Rank
    var suit: Suit

    func simpleDescription() -> String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }

    func createDeck() -> [Card] {
        var deck: [Card] = []
        for rank in Rank.Ace.rawValue...Rank.King.rawValue {
            for suit in Suit.Spades.rawValue...Suit.Clubs.rawValue {
                let card = Card(rank: Rank(rawValue: rank)!, suit: Suit(rawValue: suit)!)
                //println(card.simpleDescription())
                deck += [card]
            }
        }
        return deck
    }
}
let threeOfSpades = Card(rank: .Three, suit: .Spades)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()
let deck = threeOfSpades.createDeck()
Hans-Peter
la source
3

Voici une méthode que j'utilise pour à la fois itérer enumet fournir plusieurs types de valeurs à partir d'unenum

enum IterateEnum: Int {
    case Zero
    case One
    case Two
    case Three
    case Four
    case Five
    case Six
    case Seven

    //tuple allows multiple values to be derived from the enum case, and
    //since it is using a switch with no default, if a new case is added,
    //a compiler error will be returned if it doesn't have a value tuple set
    var value: (french: String, spanish: String, japanese: String) {
        switch self {
        case .Zero: return (french: "zéro", spanish: "cero", japanese: "nuru")
        case .One: return (french: "un", spanish: "uno", japanese: "ichi")
        case .Two: return (french: "deux", spanish: "dos", japanese: "ni")
        case .Three: return (french: "trois", spanish: "tres", japanese: "san")
        case .Four: return (french: "quatre", spanish: "cuatro", japanese: "shi")
        case .Five: return (french: "cinq", spanish: "cinco", japanese: "go")
        case .Six: return (french: "six", spanish: "seis", japanese: "roku")
        case .Seven: return (french: "sept", spanish: "siete", japanese: "shichi")
        }
    }

    //Used to iterate enum or otherwise access enum case by index order.
    //Iterate by looping until it returns nil
    static func item(index: Int) -> IterateEnum? {
        return IterateEnum.init(rawValue: index)
    }

    static func numberFromSpanish(number: String) -> IterateEnum? {
        return findItem { $0.value.spanish == number }
    }

    //use block to test value property to retrieve the enum case        
    static func findItem(predicate: ((_: IterateEnum) -> Bool)) -> IterateEnum? {

        var enumIndex: Int = -1
        var enumCase: IterateEnum?

        //Iterate until item returns nil
        repeat {
            enumIndex += 1
            enumCase = IterateEnum.item(index: enumIndex)

            if let eCase = enumCase {

                if predicate(eCase) {
                    return eCase
                }
            }
        } while enumCase != nil
        return nil
    }
}

var enumIndex: Int = -1
var enumCase: IterateEnum?

// Iterate until item returns nil
repeat {
    enumIndex += 1
    enumCase = IterateEnum.item(index: enumIndex)
    if let eCase = enumCase {
        print("The number \(eCase) in french: \(eCase.value.french), spanish: \(eCase.value.spanish), japanese: \(eCase.value.japanese)")
    }
} while enumCase != nil

print("Total of \(enumIndex) cases")

let number = IterateEnum.numberFromSpanish(number: "siete")

print("siete in japanese: \((number?.value.japanese ?? "Unknown"))")

Voici la sortie:

Le numéro zéro en français: zéro, espagnol: cero, japonais: nuru
Le numéro un en français: un, espagnol: uno, japonais: ichi
Le numéro deux en français: deux, espagnol: dos, japonais: ni
Le numéro trois en français : trois, espagnol: tres, japonais: san
Le numéro quatre en français: quatre, espagnol: cuatro, japonais: shi
Le numéro cinq en français: cinq, espagnol: cinco, japonais: allez
Le numéro six en français: six, espagnol: seis, japonais: roku
The number Seven en français: sept, espagnol: siete, japonais: shichi

Total de 8 cas

siete en japonais: shichi


MISE À JOUR

J'ai récemment créé un protocole pour gérer l'énumération. Le protocole nécessite une énumération avec une valeur brute Int:

protocol EnumIteration {

    //Used to iterate enum or otherwise access enum case by index order. Iterate by looping until it returns nil

    static func item(index:Int) -> Self?
    static func iterate(item:((index:Int, enumCase:Self)->()), completion:(()->())?) {
    static func findItem(predicate:((enumCase:Self)->Bool)) -> Self?
    static func count() -> Int
}

extension EnumIteration where Self: RawRepresentable, Self.RawValue == Int {

    //Used to iterate enum or otherwise access enum case by index order. Iterate by looping until it returns nil
    static func item(index:Int) -> Self? {
        return Self.init(rawValue: index)
    }

    static func iterate(item:((index:Int, enumCase:Self)->()), completion:(()->())?) {

        var enumIndex:Int = -1
        var enumCase:Self?

        //Iterate until item returns nil
        repeat {
            enumIndex += 1
            enumCase = Self.item(enumIndex)

            if let eCase = enumCase {
                item(index: enumIndex, enumCase: eCase)
            }
        } while enumCase != nil
        completion?()
    }

    static func findItem(predicate:((enumCase:Self)->Bool)) -> Self? {

        var enumIndex:Int = -1
        var enumCase:Self?

        //Iterate until item returns nil
        repeat {
            enumIndex += 1
            enumCase = Self.item(enumIndex)

            if let eCase = enumCase {

                if predicate(enumCase:eCase) {
                    return eCase
                }
            }
        } while enumCase != nil
        return nil
    }

    static func count() -> Int {
        var enumIndex:Int = -1
        var enumCase:Self?

        //Iterate until item returns nil
        repeat {
            enumIndex += 1
            enumCase = Self.item(enumIndex)
        } while enumCase != nil

        //last enumIndex (when enumCase == nil) is equal to the enum count
        return enumIndex
    }
}
MSimic
la source
2

Cela semble être un hack mais si vous utilisez des valeurs brutes, vous pouvez faire quelque chose comme ça

enum Suit: Int {  
    case Spades = 0, Hearts, Diamonds, Clubs  
 ...  
}  

var suitIndex = 0  
while var suit = Suit.fromRaw(suitIndex++) {  
   ...  
}  
KenH
la source
2

Tout en traitant Swift 2.0ici est ma suggestion:

J'ai ajouté le type brut à Suit enum

enum Suit: Int {

puis:

struct Card {
    var rank: Rank
    var suit: Suit


    func fullDeck()-> [Card] {

        var deck = [Card]()

        for i in Rank.Ace.rawValue...Rank.King.rawValue {

            for j in Suit.Spades.rawValue...Suit.Clubs.rawValue {

                deck.append(Card(rank:Rank(rawValue: i)! , suit: Suit(rawValue: j)!))
            }
        }

        return deck
    }
}
Aladin
la source
2

Comme avec @Kametrixom, répondez ici je pense que le retour d'un tableau serait mieux que le retour d'AnySequence, car vous pouvez avoir accès à tous les avantages d'Array tels que le nombre, etc.

Voici la réécriture:

public protocol EnumCollection : Hashable {}
extension EnumCollection {
    public static func allValues() -> [Self] {
        typealias S = Self
        let retVal = 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
            }
        }

        return [S](retVal)
    }
}
Peymankh
la source
2

Une autre solution:

enum Suit: String {
    case spades = "♠"
    case hearts = "♥"
    case diamonds = "♦"
    case clubs = "♣"

    static var count: Int {
        return 4   
    }

    init(index: Int) {
        switch index {
            case 0: self = .spades
            case 1: self = .hearts
            case 2: self = .diamonds
            default: self = .clubs
        }
    }
}

for i in 0..<Suit.count {
    print(Suit(index: i).rawValue)
}
Miguel Gallego
la source
2

Ceci est un assez vieux post de Swift 2.0. Il existe maintenant de meilleures solutions ici qui utilisent les nouvelles fonctionnalités de swift 3.0: Itération via une énumération dans Swift 3.0

Et sur cette question, il existe une solution qui utilise une nouvelle fonctionnalité de (le pas encore publié au moment où j'écris cette modification) Swift 4.2: Comment puis-je obtenir le nombre d'une énumération Swift?


Il existe de nombreuses bonnes solutions dans ce fil et d'autres, mais certaines d'entre elles sont très compliquées. J'aime simplifier autant que possible. Voici une solution qui peut ou non fonctionner pour différents besoins mais je pense qu'elle fonctionne bien dans la plupart des cas:

enum Number: String {
    case One
    case Two
    case Three
    case Four
    case EndIndex

    func nextCase () -> Number
    {
        switch self {
        case .One:
            return .Two
        case .Two:
            return .Three
        case .Three:
            return .Four
        case .Four:
            return .EndIndex

        /* 
        Add all additional cases above
        */
        case .EndIndex:
            return .EndIndex
        }
    }

    static var allValues: [String] {
        var array: [String] = Array()
        var number = Number.One

        while number != Number.EndIndex {
            array.append(number.rawValue)
            number = number.nextCase()
        }
        return array
    }
}

Pour itérer:

for item in Number.allValues {
    print("number is: \(item)")
}
Abbey Jackson
la source
1
Cela ressemble à beaucoup de travail spécifique à l'énumération individuelle que vous avez créée - je ne suis pas sûr que le retour [Number.One.rawValue, Number.Two.rawValue, ...] n'est pas plus propre, dans ce cas .
Scott Austin
Ceci est un assez vieux post de Swift 2.0. Il y a maintenant de meilleures solutions ici qui utilisent les nouvelles fonctionnalités de swift 3.0: stackoverflow.com/questions/41352594/… Et sur cette question, il existe une solution qui utilise une nouvelle fonctionnalité de (la non encore publiée au moment où j'écris cette modification ) Swift 4.2: stackoverflow.com/questions/27094878/…
Abbey Jackson
2

Les énumérations ont toRaw()et les fromRaw()méthodes. Donc, si votre valeur brute est un Int, vous pouvez répéter du premier au dernier enum:

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"
        }
    }
}

for i in Suit.Spades.toRaw()...Suit.Clubs.toRaw() {
    if let covertedSuit = Suit.fromRaw(i) {
        let description = covertedSuit.simpleDescription()
    }
}

Un problème est que vous devez tester les valeurs facultatives avant d'exécuter la simpleDescriptionméthode.Nous définissons donc convertedSuitd'abord notre valeur, puis définissons une constante surconvertedSuit.simpleDescription()

Adrian Harris Crowne
la source
2
La question d'origine portait sur une énumération de type chaîne et non sur Int
cynistersix
2

Voici mon approche suggérée. Ce n'est pas complètement satisfaisant (je suis très nouveau pour Swift et OOP!) Mais peut-être que quelqu'un peut l'affiner. L'idée est que chaque énumération fournisse ses propres informations de plage au fur .firstet à mesure des .lastpropriétés. Il ajoute seulement deux lignes de code à chaque énumération: toujours un peu codé en dur, mais au moins, il ne reproduit pas l'ensemble. Cela nécessite de modifier l' Suiténumération pour être un Int comme l' Rankénumération, au lieu de non typé.

Plutôt que de faire écho à toute la solution, voici le code que j'ai ajouté à l' .énumération, quelque part après les déclarations de cas (l' Suiténumération est similaire):

var first: Int { return Ace.toRaw() }
var last: Int { return King.toRaw() }

et la boucle que j'ai utilisée pour construire le deck comme un tableau de String. (La définition du problème ne précisait pas comment le pont devait être structuré.)

func createDeck() -> [String] {
    var deck: [String] = []
    var card: String
    for r in Rank.Ace.first...Rank.Ace.last {
        for s in Suit.Hearts.first...Suit.Hearts.last {
            card = Rank.simpleDescription( Rank.fromRaw(r)!)() + " of " + Suit.simpleDescription( Suit.fromRaw(s)!)()
           deck.append( card)
       }
    }
    return deck
}

Ce n'est pas satisfaisant car les propriétés sont associées à un élément plutôt qu'à l'énumération. Mais cela ajoute de la clarté aux boucles «for». J'aimerais qu'il dise Rank.firstau lieu de Rank.Ace.first. Cela fonctionne (avec n'importe quel élément), mais c'est moche. Quelqu'un peut-il montrer comment élever cela au niveau énuméré?

Et pour le faire fonctionner, j'ai retiré la createDeckméthode de la structure de la carte. Je ne pouvais pas comprendre comment obtenir un tableau [String] retourné à partir de cette structure, et cela semble de toute façon un mauvais endroit pour mettre une telle méthode.

Roger Worden
la source
2

Je l'ai fait en utilisant la propriété calculée, qui renvoie le tableau de toutes les valeurs (grâce à cet article http://natecook.com/blog/2014/10/loopy-random-enum-ideas/ ). Cependant, il utilise également des valeurs brutes int, mais je n'ai pas besoin de répéter tous les membres de l'énumération dans une propriété distincte.

UPDATE Xcode 6.1 a un peu changé la façon d'obtenir un membre enum rawValue, donc j'ai corrigé la liste. Correction également d'une petite erreur avec un mauvais premier rawValue.

enum ValidSuits: Int {
    case Clubs = 0, Spades, Hearts, Diamonds
    func description() -> String {
        switch self {
        case .Clubs:
            return "♣︎"
        case .Spades:
            return "♠︎"
        case .Diamonds:
            return "♦︎"
        case .Hearts:
            return "♥︎"
        }
    }

    static var allSuits: [ValidSuits] {
        return Array(
            SequenceOf {
                () -> GeneratorOf<ValidSuits> in
                var i=0
                return GeneratorOf<ValidSuits> {
                    return ValidSuits(rawValue: i++)
                }
            }
        )
    }
}
gleb vodovozov
la source
1
enum Rank: Int
{
    case Ace = 0
    case Two, Three, Four, Five, Six, Seve, Eight, Nine, Ten
    case Jack, Queen, King
    case Count
}

enum Suit : Int
{
    case Spades = 0
    case Hearts, Diamonds, Clubs
    case Count
}

struct Card
{
    var rank:Rank
    var suit:Suit
}

class Test
{
    func makeDeck() -> Card[]
    {
        let suitsCount:Int = Suit.Count.toRaw()
        let rankCount:Int = Rank.Count.toRaw()
        let repeatedCard:Card = Card(rank:Rank.Ace, suit:Suit.Spades)
        let deck:Card[] = Card[](count:suitsCount*rankCount, repeatedValue:repeatedCard)

        for i:Int in 0..rankCount
        {
            for j:Int in 0..suitsCount
            {
                deck[i*suitsCount+j] = Card(rank: Rank.fromRaw(i)!, suit: Suit.fromRaw(j)!)
            }
        }
        return deck
    }
}

Basé sur la réponse de Rick: c'est 5 fois plus rapide

John Lluch-Zorrilla
la source
L'ajout d'un Countboîtier interrompra les switchmises en œuvre et la CaseIterableconformité exhaustives .
Cœur