Jak wyliczyć wyliczenie typu String?


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

Na przykład, jak mogę zrobić coś takiego:

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

Wynikowy przykład:



W jakim przypadku nie znasz typu?
mtbennett

Masz rację, w tym przypadku jest to typ String.
Lucien

Brak refleksji w Swift jeszcze ...
Sulthan

22
Czy to nie ironiczne, że nazywane są wyliczeniami, ale są tak boleśnie denerwujące, by wyliczyć je w Szybkim?
Charlton Provatas

3
@CharltonProvatas Gdyby to była jedyna wada w Swift, nazwałbym to dniem. Patrząc na to, ile osób oferuje różne obejścia tego problemu , po prostu gryzę klawiaturę.
qwerty_so

Odpowiedzi:


266

Swift 4.2+

Począwszy od Swift 4.2 (z Xcode 10), po prostu dodaj zgodność protokołu, CaseIterableaby skorzystać allCases. Aby dodać tę zgodność protokołu, wystarczy gdzieś napisać:

extension Suit: CaseIterable {}

Jeśli wyliczenie jest twoje, możesz określić zgodność bezpośrednio w deklaracji:

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

Następnie poniższy kod wydrukuje wszystkie możliwe wartości:

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

Kompatybilność z wcześniejszymi wersjami Swift (3.x i 4.x)

Jeśli potrzebujesz obsługi Swift 3.x lub 4.0, możesz naśladować implementację Swift 4.2, dodając następujący kod:

#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

@DmitryPetukhov Z przyjemnością pomogę, ale: (1) czy na pewno masz najnowszą wersję kodu? (niektóre awarie zostały naprawione miesiąc temu) i (2) podaj MCVE niestandardowego typu, który może odtworzyć awarię, oraz swoją wersję Xcode.
Cœur

Działa to dla mnie dobrze w przypadku kompilacji debugowania, ale gdy tylko utworzę wersję i przesyłam do TestFlight, następuje awaria. Czy Apple jakoś to rozbija?
Daniel Wood,

1
Wygląda na to, że twoja wersja ma jeden pozytyw w stosunku do wbudowanej wersji CaseIterator. Mogę rozszerzyć wyliczenia zdefiniowane w innym pliku z twoją wersją. Jeśli upublicznisz wszystkie przypadki w rozszerzeniu, możesz także rozszerzyć wyliczenia zdefiniowane w różnych ramach.
adamfowlerphoto

1
@CyberMew Zaktualizowałem odpowiedź, aby ją wyjaśnić. Książka Swift i uwagi do wydania Xcode 10 o CaseIterable są późniejsze od mojej odpowiedzi i wykorzystały uproszczone przykłady, w których nie ma poparcia dla enum String, w przeciwieństwie do obecnego pytania dotyczącego przepełnienia stosu.
Cœur

1
Chciałbym podkreślić znaczenie „# if! Swift (> = 4.2)”. Jeśli napisałeś swój kod przed wersją swift 4.2 i zapomniałeś usunąć kod poniżej „# if! Swift (> = 4.2)”, kiedy używasz Xcode w wersji 11.4 do budowania lokalnie na urządzeniu testowym, wszystko będzie dobrze. Ale gdy aplikacja zostanie pobrana ze sklepu z aplikacjami lub lotu testowego, ten fragment kodu spowoduje awarię aplikacji. Tego rodzaju błąd jest bardzo trudny do wykrycia lub debugowania.
Oliver Zhang

524

Ten post jest tutaj istotny https://www.swift-studies.com/blog/2014/6/10/enumerating-enums-in-swift

Zasadniczo proponowane rozwiązanie to

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

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

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

188
Fajnie, ale ... musisz wprowadzić swoje elementy wyliczenia dwa razy - raz dla wyliczenia, raz dla wszystkichWartości. Niezupełnie tak elegancki, jak by się chciało.
Jay Imerman,

2
Zgadzam się z „ale” ... jednak, jak stwierdzono w artykule, być może istnieje problem, że wyliczenie jest naprawdę zbiorem, a zatem nieuporządkowanym ... pamiętaj, że ... zdefiniowane przypadki zamówień nie byłyby złym początkiem!
rougeExciter

3
W Javie kompilator robi to za Ciebie, może Swift 2.0 to zrobi. W szczególności w Javie wszystkie wyliczenia otrzymują metodę opisu (toString in Java), która podaje ciąg znaków jako nazwy spraw (Podkładki, ...), a zestaw spraw jest tworzony automatycznie. Java zapewnia także indeksowanie pozycyjne. Jak powiedziałem, może Swift 2.0.
Howard Lovatt

1
Idealnie byłoby mieć coś podobnego do implementacji c #, w której możesz to zrobić, Enum.Values(typeof(FooEnum))ale ujawniono jako metodę rozszerzenia (np. Mapuj lub zmniejsz). FooEnum.values() :: values(EnumType -> [EnumType])
rodrigoelp

1
Artykuł na końcu dobrze rozumie, że każda wartość wyliczenia znajduje się w tablicy allValues ​​za pomocą testu jednostkowego. Nadal jednak widzę, jak ktoś dodaje więcej elementów, ale nie wymusza ich w teście jednostkowym, co wciąż pozostawia nas z powrotem na początku, nie wiedząc na pewno, że wszystkie wartości wyliczenia są przechowywane we wszystkich wartościach.
DonnaLea

278

Stworzyłem funkcję użyteczną iterateEnum()do iterowania spraw dla dowolnych enumtypów.

Oto przykładowe użycie:

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

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

Które wyjścia:

♠
♥
♦
♣

Ale służy to tylko do debugowania lub testowania : zależy to od kilku nieudokumentowanych zachowań kompilatora Swift1.1, więc używaj go na własne ryzyko.

Oto kod:

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

Podstawową ideą jest:

  • Reprezentacja pamięci enum, wyłączając enums z powiązanymi typami, jest tylko indeksem przypadków, gdy liczba przypadków jest 2...256identyczna z UInt8, kiedy 257...65536, to UInt16i tak dalej. Może więc pochodzić unsafeBitcastz odpowiadających typów całkowitych bez znaku.
  • .hashValue wartości wyliczeniowych jest taki sam jak indeks sprawy.
  • .hashValuez wartości wyliczeniowych bitcastowanych z niepoprawnego indeksu to 0.

Zmieniono dla Swift2 i wprowadzono pomysły rzutowania z odpowiedzi @ 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
    }
}

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

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

20
Niesamowita i jedyna odpowiedź, która odpowiada na pytanie! Ale tak ... nie dotknę tego! +1 za wysiłek!
dotToString 12.03.15

Właśnie opublikowałem odpowiedź, która działa w zasadzie w ten sam sposób (zobaczyłem tę odpowiedź później). Wykorzystuje Swift 2.0 beta 6 i nowoczesne funkcje języka.
Kametrixom

2
Wersja Swift 3 działa dobrze. Wystarczy trochę zmodyfikować użycie: dla f w iterateEnum (Suit.self) {print (f.rawValue)}
Gene De Lisa

16
+1 to jest całkiem genialne. Jest również, IMHO, zbyt sprytny, aby go używać, o czym świadczy znaczące zerwanie przy każdej głównej zmianie wersji Swift. Trzeba przyznać autorowi, że wersja Swift 3 została wykonana na miesiąc przed wypuszczeniem wersji Swift 3 z wersji beta ... Jeśli zamierzasz wziąć tę odpowiedź i nauczyć się tego wszystkiego withUnsafePointer withMemoryReboundi innych pointeerzeczy, użyj tego za wszelką cenę. W przeciwnym razie uniknęłbym tego.
Dan Rosenstark,

5
Chcę tylko dodać, że teraz jest zepsuty w swift 4, ale tylko w Linuksie, więc +1 do powyższych komentarzy, że jest to zbyt sprytne, aby z niego korzystać.
Andrew Plummer,

130

Inne rozwiązania działają, ale wszystkie przyjmują na przykład liczbę możliwych rang i kolorów lub jaka może być pierwsza i ostatnia ranga. To prawda, że ​​układ talii kart prawdopodobnie nie zmieni się znacząco w dającej się przewidzieć przyszłości. Ogólnie jednak fajniej jest pisać kod, który przyjmuje możliwie najmniejsze założenia. Moje rozwiązanie:

Dodałem surowy typ do Suitwyliczenia, dzięki czemu mogę uzyskać Suit(rawValue:)dostęp do Suitskrzynek:

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

Poniżej implementacja metody Karty createDeck(). init(rawValue:)jest inicjalizatorem awaryjnym i zwraca opcjonalny. Po rozpakowaniu i sprawdzeniu jego wartości w obu instrukcjach while nie trzeba przyjmować liczby Ranklub Suitprzypadków:

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

Oto jak wywołać createDeckmetodę:

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

12
Absolutnie najlepsza odpowiedź, jaką widziałem w różnych wątkach na ten temat. Bardzo elegancko. Działa to z wyliczeniami typów Int, ale zastanawiam się, jak można iterować po innych typach (ciąg znaków, typy niestandardowe itp.).
Jay Imerman,

3
To zdecydowanie najlepsze rozwiązanie. Jedną rzecz do zapamiętania. W przykładzie w książce nie ma „case Spades = 1”, jak ma to sdduursma. Z początku tego nie złapałem. jest to jedna z opcji lub możesz po prostu użyć „var m = 0”
Joshua Goossen,

10
To powoduje założenie, że surowe wartości są sekwencyjne. Gdy nie jest to prawdą, na przykład gdy wyliczenie reprezentuje flagi maski bitowej, pętla kończy się przedwcześnie.
Jim

1
To rozwiązanie zakłada, że ​​możesz zmodyfikować definicję Suit. Możesz w tym przykładzie, ale ćwiczenie miało zmusić cię do pracy z tym, enumsco otrzymałeś, jakby pochodziło z zewnętrznego źródła.
Josh J

1
Jedyne, na co chcę narzekać, to to, że nie mogę nazwać tego metodą statyczną, ale najpierw muszę utworzyć obiekt karty.
qed

76

Potknąłem się w bitach i bajtach i stworzyłem rozszerzenie, które później odkryłem, działa bardzo podobnie do odpowiedzi @rintaro . Używa się go w następujący sposób:

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

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

Niezwykłe jest to, że można go stosować na dowolnym wyliczeniu bez powiązanych wartości. Zauważ, że to nie działa dla wyliczeń, które nie mają spraw.

Podobnie jak w przypadku odpowiedzi @rintaro , ten kod wykorzystuje reprezentację wyliczenia. Ta reprezentacja nie jest udokumentowana i może ulec zmianie w przyszłości, co może ją zepsuć. Nie polecam wykorzystania tego w produkcji.

Kod (Swift 2.2, Xcode 7.3.1, nie działa na 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
            }
        }
    }
}

Kod (Swift 3, Xcode 8.1, nie działa na 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
            }
        }
    }
}

Nie mam pojęcia, dlaczego potrzebuję typealias, ale kompilator narzeka bez tego.


10
Ta odpowiedź jest nawet lepsza od mojej, zwłaszcza w części castingowej :)
rintaro,

Ale myślę, że to działa tylko na małym środowisku Endian?
rintaro

5
Xcode 8 beta 6 ponownie to zmienił! Pojawia się następujący błąd: „init” jest niedostępny: użyj „withMemoryRebound (do: pojemność: _)”, aby tymczasowo wyświetlić pamięć jako inny typ zgodny z układem.
Zmieszany Vorlon

1
@ConfusedVorlon: patrz odpowiedź powyżej przez @Rintaro: zastąp withUnsafePointerpointee}przezwithUnsafePointer(to: &i) { $0.withMemoryRebound(to: T.self, capacity: 1) { $0.pointee } }
Stefana

1
Wygląda na to, że nie działa już od Xcode 10. (Wiem, że nie byłoby to wymagane w Swift 4.2), ale gdy używasz Swift 4 (.1) w Xcode 10, ten kod nie działa (surowa wartość jest nierówna)
benrudhart

26

Możesz iterować przez wyliczanie, implementując ForwardIndexTypeprotokół.

ForwardIndexTypeProtokół wymaga, aby zdefiniować successor()funkcję kroku przez elementy.

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

Iteracja w zakresie otwartym lub zamkniętym ( ..<lub ...) wywoła wewnętrznie successor()funkcję, która pozwala napisać:

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

2
Uważam, że jest to najbardziej „właściwa” odpowiedź na pytanie, nawet najbardziej „elegancka” (konieczny dodatkowy kod w porównaniu z innymi opcjami tutaj znanymi), biorąc pod uwagę wynikową składnię, gdy jest używana w zakresie (składnia jest tym, co ja spodziewałby się, że będzie w stanie to zrobić, jeśli wyliczenia, w których można obejść bezliczeniowe sansy). Dzięki! Chociaż warto zauważyć, że jeśli przeciążenie operatora jest stosowane w innym miejscu niż sukcesor () (co wydaje się kuszące), to oczywiście wymuszone rozpakowywanie jest niebezpieczne. Ponadto, ta poprawka wydaje się niepotrzebna ...?
iOS Gamer

Zaktualizowana odpowiedź w celu odzwierciedlenia najnowszych specyfikacji języka Swift
RndmTsk

Prawidłowo zdefiniowana successor()metoda (pierwsza opcja) wyeliminuje potrzebę enumposiadania powiązanego typu. +1
nhgrif

1
Ale ta elegancka odpowiedź nie zadziała w przypadku wyliczeń smyczkowych, prawda?
Ali

Najbardziej „właściwe” / najlepsze rozwiązanie! + 1-ed
Siu Ching Pong -Asuka Kenji-

18

Ten problem jest teraz znacznie łatwiejszy. Oto moje rozwiązanie 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
}

Przed 4.2:

Podoba mi się to rozwiązanie, które połączyłem po znalezieniu „ Listy ze zrozumieniem w Swift ”.

Używa surowych Int zamiast Ciągów, ale unika podwójnego pisania, pozwala dostosowywać zakresy i nie zapisuje surowych wartości kodu.

To jest wersja Swift 4 mojego oryginalnego rozwiązania, ale zobacz ulepszenie 4.2 powyżej:

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
}

Och, teraz widzę, że mój jest zasadniczo taki sam jak Sutean Rutjanalard.
adazacom,

1
W rzeczywistości bardziej podobała mi się twoja implementacja. Myślę, że to jest jaśniejsze! 1 głosowanie pozytywne. W rzeczywistości najczęściej głosowane odpowiedzi są zbyt sprytne i z pewnością złamią się w przyszłości. Twoja obiecuje pewną stabilność w przyszłości.
jvarela,

17

Zasadniczo można to zrobić w ten sposób, zakładając, że nie używasz przypisania wartości surowych dla przypadków 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())")
}

7
Jest to miłe, ale działa tylko w przypadku ciągłego wyliczania liczb całkowitych, zaczynając od 0
Robert

@Robert, jak stwierdził mój komentarz powyżej: „nie używasz przypisania wartości surowych dla przypadków enum”
Alfa07,

Tak - nie używaj surowych wartości, a także ustaw typ podstawowy na int. Szybko nie potrzebujesz typu dla wyliczenia, jak w przykładzie koloru. enum ItWontWorkForThisEnum {case a, b, c}
Robert,

Jak rozwiązałoby to problem, jeśli krotka jest powiązana z przypadkiem wyliczenia?
rougeExciter

Nie można bardzo łatwo powiązać krotki z wyliczeniem.
nhgrif

13

Jeśli podasz enum surową wartość Int znacznie ułatwi zapętlenie.

Na przykład możesz użyć, anyGeneratoraby uzyskać generator, który może wyliczyć wszystkie wartości:

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())

Wygląda to jednak na dość powszechny wzorzec, czy nie byłoby miło, gdybyśmy mogli wyliczyć dowolny typ wyliczeniowy, po prostu dostosowując się do protokołu? Dobrze z Swift 2.0 i rozszerzeniami protokołu, teraz możemy!

Po prostu dodaj to do swojego projektu:

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

Teraz za każdym razem, gdy tworzysz wyliczenie (o ile ma wartość surową Int), możesz uczynić ją wyliczalną, postępując zgodnie z protokołem:

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

Jeśli twoje wartości wyliczeniowe nie zaczynają się od 0(domyślne), zastąp firstRawValuemetodę:

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

Ostateczna klasa garnitur, włącznie z wymianą simpleDescriptionz bardziej standardowym protokołem CustomStringConvertible będzie wyglądać następująco:

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

Składnia 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
    }
}

nextIndex ++ zostanie usunięty w trybie szybkim 3. Co sugerujesz jako zamiennik - var nextIndex = firstRawValue () zwraca anyGenerator {Self (rawValue: nextIndex ++)}
Avi

Domyśliłam się. odłóż {nextIndex + = 1} return AnyGenerator {Self (rawValue: nextIndex)}
Avi

12

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

Zaktualizowano kod do formularza Swift 2.2 z odpowiedzi Kametrixom

Dla Swift 3.0+ ( wielkie dzięki dla @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
        }
    }
}

@silvansky, czy mógłbyś wyjaśnić, co masz na myśli?
ale_stro

Ups, przepraszam, sprawdziłem ponownie i wystąpił błąd na placu zabaw: w prawdziwym projekcie ten kod działa zgodnie z oczekiwaniami, dzięki! =)
silvansky

1
Świetne rozwiązanie! Działa również dobrze na Swift 3 z minimalnymi zmianami („AnyGenerator” przemianowano na „AnyIterator” i „.memory” przemianowano na „.pointee”).
Philip

9

Rozwiązanie 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)
}

7

Odkryłem, że robię .allValuesdużo w całym moim kodzie. W końcu wymyśliłem sposób, aby po prostu dostosować się do Iteratableprotokołu i mieć rawValues()metodę.

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

Xcode 10 z 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 }
}

Nazwać

print(Filter.allValues)

Wydruki:

[„Wynagrodzenie”, „Doświadczenie”, „Technologia”, „Niewykorzystany”, „Niewykorzystany”


starsza wersja

Do enumreprezentowaniaInt

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

Nazwij to tak:

print(Filter.allValues)

Wydruki:

[0, 1, 2, 3, 4]


Do enumreprezentowaniaString

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

Nazwać

print(Filter.allValues)

Wydruki:

[„Wynagrodzenie”, „Doświadczenie”, „Technologia”, „Niewykorzystany”, „Niewykorzystany”


7

EDYCJA: Swift Evolution Wniosek SE-0194 Zbiór skrzynek Enum proponuje jednoznaczne rozwiązanie tego problemu. Widzimy to w Swift 4.2 i nowszych. Wniosek wskazuje również na niektóre obejścia podobne do już wspomnianych tutaj, ale mimo wszystko warto je zobaczyć.

Zachowam również mój oryginalny post ze względu na kompletność.


Jest to kolejne podejście oparte na odpowiedzi @ Peymmankh, dostosowane do 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)
}

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

Co powiesz na to?


Dzięki, działa tak jak potrzebuję. Ale czy jest jakaś szansa na zamknięcie mapy, aby uzyskać wartość nie według indeksu, ale według nazwy?
Michaił

4

Możesz spróbować wyliczyć w ten sposób

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]

1
To dużo niepotrzebnego kodu! Jest to równoważne z tym static var enumerate = [Mercury, Venus, Earth, Mars], że jest to odpowiedź słaba w porównaniu do większości głosowanych odpowiedzi stackoverflow.com/a/24137319/1033581
Cur

@ Cœur ta odpowiedź ma ważną zaletę korzystania z kompilatora, aby zagwarantować, że nie przegapisz żadnej sprawy.
dakakarov

@ Cœur, który ma ten sam problem, że pozwala popełnić błąd użytkownika, tzn. Kompilator nie narzeka, jeśli napiszesz return [Mercury, Venus, Mars]zamiastreturn [Mercury, Venus, Earth, Mars]
dchakarov

@dchakarov Postanowiłem opublikować poprawkę jako odpowiedź, dla jasności: stackoverflow.com/a/50409525/1033581
Cur

@ Cœur Jeśli w nowej odpowiedzi zastąpisz instrukcję return return [.spades, .hearts, .clubs]kompilatorem nic nie powiesz, a następnie, gdy spróbujesz użyć jej w kodzie, otrzymasz [TestApp.Suit.spades, TestApp.Suit.hearts, TestApp.Suit.clubs]- o to mi chodziło - że jeśli masz do czynienia z dużym Wylicz i musisz od czasu do czasu dodawać lub usuwać przypadki, twoje rozwiązanie jest podatne na błędy pominięcia, podczas gdy bieżąca odpowiedź, choć nie jest zwięzła, jest bezpieczniejsza.
dchakarov

4

W Swift 3, gdy ma wyliczenie bazowe rawValue, możesz zaimplementować Strideableprotokół. Zaletą jest to, że nie są tworzone tablice wartości jak w niektórych innych sugestiach i że działa standardowa pętla Swift „for in”, co stanowi niezłą składnię.

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

Ahh, właśnie tego szukałem, aby zastąpić ForwardIndexType. Teraz moje iteracje wyglądają dobrze na stronie użytkowania ... właściwie po prostu Swifty.
Andrew Duncan,

4

To rozwiązanie zapewnia właściwą równowagę czytelności i łatwości konserwacji.

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()

Moim zdaniem jest to najlepsze rozwiązanie. Kiedy widzę szybki kod, głównie czytelność nie jest lepsza niż objc. Ale może tak być, jeśli programiści zwrócą większą uwagę na czytelników swojego kodu. Ich przyszłość, na przykład :)
Vilém Kurz

4

Przepraszam, moja odpowiedź była konkretna dla tego, jak wykorzystałem ten post w tym, co musiałem zrobić. Dla tych, którzy natkną się na to pytanie, szukając sposobu na znalezienie przypadku w wyliczeniu, jest to sposób, aby to zrobić (nowość w Swift 2):

Edycja: camelCase z małymi literami jest teraz standardem dla wartości wyliczeniowych 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)
        }
    }

Dla tych, którzy zastanawiają się nad wyliczaniem w wyliczeniu, odpowiedzi podane na tej stronie, które obejmują statyczny var / let zawierający tablicę wszystkich wartości wyliczeniowych, są poprawne. Najnowszy przykładowy kod Apple dla tvOS zawiera dokładnie tę samą technikę.

To powiedziawszy, powinni zbudować bardziej wygodny mechanizm języka (Apple, słuchasz?)!


3

Eksperyment był następujący: DOŚWIADCZENIE

Dodaj metodę do karty, która tworzy pełną talię kart, z jedną kartą dla każdej kombinacji rangi i koloru.

Więc bez modyfikowania lub ulepszania danego kodu poza dodaniem metody (i bez użycia rzeczy, których jeszcze nie nauczono), wymyśliłem to rozwiązanie:

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()

3

Oto metoda, której używam zarówno do iteracji, jak enumi zapewniania wielu typów wartości z jednegoenum

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

To jest wynik:

Liczba zero po francusku: zéro, hiszpański: cero, japoński: nuru
Liczba jeden po francusku: un, hiszpański: uno, japoński: ichi
Liczba dwa po francusku: deux, hiszpański: dos, japoński: ni
Liczba trzy po francusku : trois, hiszpański: tres, japoński: san
Numer cztery po francusku: quatre, hiszpański: cuatro, japoński: shi
Numer pięć po francusku: cinq, hiszpański: cinco, japoński: go
Numer sześć po francusku: sześć, hiszpański: seis, japanese: roku
Number Seven po francusku: sept, spanish: siete, japanese: shichi

Łącznie 8 przypadków

siete po japońsku: shichi


AKTUALIZACJA

Niedawno utworzyłem protokół do obsługi wyliczenia. Protokół wymaga wyliczenia o wartości surowej 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
    }
}

2

To wygląda na włamanie, ale jeśli używasz surowych wartości, możesz zrobić coś takiego

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

var suitIndex = 0  
while var suit = Suit.fromRaw(suitIndex++) {  
   ...  
}  

2

Podczas zajmowania się Swift 2.0tutaj jest moja sugestia:

Dodałem typ surowy do Suit enum

enum Suit: Int {

następnie:

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

2

Tak jak w przypadku @Kametrixom odpowiedź tutaj uważam, że zwrócenie tablicy byłoby lepsze niż zwrócenie AnySequence, ponieważ możesz mieć dostęp do wszystkich korzyści Array, takich jak liczba itp.

Oto przepis:

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

2

Inne rozwiązanie:

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

2

To dość stary post z Swift 2.0. Istnieją teraz lepsze rozwiązania, które wykorzystują nowsze funkcje swift 3.0: Iteracja przez Enum w Swift 3.0

I w tym pytaniu istnieje rozwiązanie, które wykorzystuje nową funkcję (jeszcze nie wydanego, gdy piszę tę edycję) Swift 4.2: Jak uzyskać liczbę wyliczeń Swift?


W tym wątku jest wiele dobrych rozwiązań, a niektóre z nich są jednak bardzo skomplikowane. Lubię maksymalnie uprościć. Oto rozwiązanie, które może, ale nie musi, działać dla różnych potrzeb, ale myślę, że działa dobrze w większości przypadków:

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

Aby iterować:

for item in Number.allValues {
    print("number is: \(item)")
}

1
To wydaje się dużo pracy, która jest specyficzna dla indywidualnego wyliczenia, które utworzyłeś - nie jestem pewien, czy zwrot [Number.One.rawValue, Number.Two.rawValue, ...] nie jest czystszy, w tym przypadku .
Scott Austin

To dość stary post z Swift 2.0. Istnieją teraz lepsze rozwiązania, które wykorzystują nowsze funkcje swift 3.0: stackoverflow.com/questions/41352594/... A w tym pytaniu istnieje rozwiązanie, które korzysta z nowej funkcji (jeszcze nie wydanej, gdy piszę tę edycję ) Swift 4.2: stackoverflow.com/questions/27094878/…
Abbey Jackson

2

Wyliczenia mają toRaw()i fromRaw()metody. Jeśli więc twoja pierwotna wartość to an Int, możesz iterować od pierwszego do ostatniego 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()
    }
}

Jedną z nich jest to, że musisz przetestować opcjonalne wartości przed uruchomieniem simpleDescriptionmetody, więc convertedSuitnajpierw ustawiamy naszą wartość, a następnie ustawiamy stałą naconvertedSuit.simpleDescription()


2
Pierwotne pytanie dotyczyło wyliczenia typu String, a nie Int
cynistersix

2

Oto moje sugerowane podejście. Nie jest całkowicie zadowalający (jestem nowy w Swift i OOP!), Ale może ktoś może to udoskonalić. Chodzi o to, aby każde wyliczenie zapewniało swój własny zakres informacji .firsti .lastwłaściwości. Dodaje tylko dwa wiersze kodu do każdego wyliczenia: wciąż trochę zakodowane, ale przynajmniej nie powiela całego zestawu. Wymaga zmodyfikowania Suitwyliczenia tak, aby było Int, tak jak Rankwyliczenie, zamiast bez typu.

Zamiast powtórzyć całe rozwiązanie, oto kod, który dodałem do .wyliczenia, gdzieś po instrukcji case ( Suitwyliczenie jest podobne):

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

i pętlę, której użyłem do zbudowania talii jako tablicy String. (Definicja problemu nie określała, w jaki sposób miała być zbudowana talia).

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
}

Jest to niezadowalające, ponieważ właściwości są powiązane raczej z elementem niż z wyliczeniem. Ale dodaje jasności pętlom „za”. Chciałbym to powiedzieć Rank.firstzamiastRank.Ace.first . Działa (z dowolnym elementem), ale jest brzydki. Czy ktoś może pokazać, jak podnieść to do poziomu wyliczenia?

Aby to zadziałało, podniosłem createDeckmetodę z struktury karty. Nie mogłem wymyślić, jak uzyskać tablicę [String] z tej struktury, i wydaje się to złe miejsce na zastosowanie takiej metody.


2

Zrobiłem to za pomocą obliczonej właściwości, która zwraca tablicę wszystkich wartości (dzięki temu postowi http://natecook.com/blog/2014/10/loopy-random-enum-ideas/ ). Jednak używa również int surowych wartości, ale nie muszę powtarzać wszystkich elementów wyliczenia w osobnej właściwości.

AKTUALIZACJA Xcode 6.1 zmienił nieco sposób uzyskiwania członka enum rawValue, więc naprawiłem listę. Naprawiono również mały błąd z niewłaściwym pierwszym 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++)
                }
            }
        )
    }
}

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

Na podstawie odpowiedzi Ricka: jest to 5 razy szybciej


Dodanie Countskrzynki złamie wyczerpujące switchimplementacje i CaseIterablezgodność.
Cœur
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.