วิธีการแจงนับ enum ด้วยชนิดของสตริงได้อย่างไร


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

ตัวอย่างเช่นฉันจะทำสิ่งที่ชอบ:

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

ตัวอย่างผลลัพธ์:



ในกรณีใดคุณจะไม่ทราบประเภท
mtbennett

คุณถูกในกรณีนี้มันเป็นประเภทสตริง
ลูเชียน

ยังไม่มีการไตร่ตรองใน Swift ...
Sulthan

22
ไม่ใช่เรื่องน่าขันที่พวกเขาเรียกว่าการแจงนับ แต่มันช่างน่ารำคาญอย่างยิ่งที่จะระบุใน Swift?
Charlton Provatas

3
@ CharltonProvatas ถ้านั่นเป็นข้อเสียเปรียบเพียงอย่างเดียวใน Swift ฉันจะเรียกมันว่าวัน ดูว่ามีกี่คนที่เสนอวิธีแก้ปัญหาที่แตกต่างกันสำหรับเรื่องนี้ฉันแค่แทะคีย์บอร์ดของฉัน
qwerty_so

คำตอบ:


267

Swift 4.2+

เริ่มต้นด้วยสวิฟท์ 4.2 (กับ Xcode 10) เพียงแค่เพิ่มโปรโตคอลสอดคล้องกับการได้รับประโยชน์จากCaseIterable allCasesในการเพิ่มความสอดคล้องของโปรโตคอลนี้คุณต้องเขียนที่ใดที่หนึ่ง:

extension Suit: CaseIterable {}

หาก Enum เป็นของคุณเองคุณสามารถระบุความสอดคล้องได้โดยตรงในการประกาศ:

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

จากนั้นรหัสต่อไปนี้จะพิมพ์ค่าที่เป็นไปได้ทั้งหมด:

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

ความเข้ากันได้กับ Swift เวอร์ชั่นก่อนหน้า (3.x และ 4.x)

หากคุณต้องการรองรับ Swift 3.x หรือ 4.0 คุณอาจเลียนแบบการใช้งาน Swift 4.2 โดยเพิ่มรหัสต่อไปนี้:

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

@DmitryPetukhov ฉันยินดีที่จะช่วย แต่: (1) คุณแน่ใจหรือว่าคุณได้รับรหัสรุ่นล่าสุด? (ความผิดพลาดบางอย่างได้รับการแก้ไขในเดือนที่ผ่านมา) และ (2) โปรดให้ MCVE ประเภทที่คุณกำหนดเองที่สามารถสร้างความผิดพลาดและรุ่น Xcode ของคุณ
Cœur

สิ่งนี้ใช้งานได้ดีสำหรับฉันในการดีบักบิลด์ แต่ทันทีที่ฉันสร้างรีลีสและอัปโหลดไปยัง TestFlight มันจะขัดข้อง แอปเปิ้ลลอกออกหรือไม่?
Daniel Wood

1
ดูเหมือนว่าเวอร์ชันของคุณจะมีข้อดีอย่างหนึ่งต่อ CaseIterator เวอร์ชัน inbuilt ฉันสามารถขยาย enums ที่กำหนดไว้ในไฟล์อื่นด้วยเวอร์ชันของคุณ หากคุณสร้าง allCases ในส่วนขยายสาธารณะคุณสามารถขยาย enums ที่กำหนดในกรอบงานต่าง ๆ ได้
adamfowlerphoto

1
@CyberMew ฉันได้อัปเดตคำตอบเพื่อชี้แจงแล้ว หนังสือ Swift และบันทึกย่อประจำรุ่น Xcode 10 กล่าวถึง CaseIterable เป็นคำตอบหลังของฉันและพวกเขาใช้ตัวอย่างแบบง่าย ๆ ที่ไม่ได้รับการสนับสนุน Enum Stringตรงข้ามกับคำถาม Stack Overflow ปัจจุบัน
Cœur

1
ฉันต้องการเน้นความสำคัญของ "# if! swift (> = 4.2)" หากคุณเขียนรหัสก่อน swift 4.2 และคุณลืมลบรหัสร้อง "# if! swift (> = 4.2)" เมื่อคุณใช้ Xcode เวอร์ชัน 11.4 เพื่อสร้างภายในเครื่องบนอุปกรณ์ทดสอบของคุณทุกอย่างจะเรียบร้อย แต่เมื่อแอพของคุณถูกดาวน์โหลดจาก app store หรือทดสอบการบินรหัสดังกล่าวจะทำให้แอปของคุณทำงานล้มเหลว จุดบกพร่องชนิดนั้นยากที่จะตรวจจับหรือดีบัก
Oliver Zhang

524

โพสต์นี้มีความเกี่ยวข้องที่นี่https://www.swift-studies.com/blog/2014/6/10/enumerating-enums-in-swift

เป็นหลักวิธีแก้ปัญหาที่เสนอคือ

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

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

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

188
ดี แต่ ... คุณต้องป้อนองค์ประกอบของการแจงนับสองครั้ง - หนึ่งครั้งสำหรับการแจงนับครั้งสำหรับค่าทั้งหมด ไม่สง่าอย่างที่ใคร ๆ ปรารถนา
Jay Imerman

2
เห็นด้วยกับ "แต่" ... อย่างไรก็ตามตามที่ระบุไว้ในบทความอาจมีปัญหาที่ enum เป็นชุดและดังนั้นจึงไม่ได้สั่ง ... ใจคุณ ... กรณีการสั่งซื้อที่กำหนดไว้ในจะไม่เริ่มต้นที่ไม่ดี!
rougeExciter

3
ใน Java คอมไพเลอร์ทำสิ่งนี้ให้คุณบางที Swift 2.0 ก็จะทำเช่นนี้เช่นกัน โดยเฉพาะอย่างยิ่งใน Java ทั้งหมด enums รับเมธอด (toString ใน Java) ที่ให้สตริงเป็นชื่อเคส (Washers, ... ) และชุดของเคสถูกสร้างขึ้นโดยอัตโนมัติ Java ยังให้การจัดทำดัชนีตำแหน่ง อย่างที่ฉันพูดบางทีอาจเป็น Swift 2.0
Howard Lovatt

1
โดยหลักการแล้วคุณจะมีสิ่งที่คล้ายกับการใช้งาน c # ซึ่งคุณสามารถทำได้Enum.Values(typeof(FooEnum))แต่ถูกเปิดเผยเป็นวิธีการขยาย (เช่นแผนที่หรือลดขนาด) FooEnum.values() :: values(EnumType -> [EnumType])
rodrigoelp

1
บทความทำให้จุดดีในตอนท้ายเกี่ยวกับการทำให้แต่ละค่า enum อยู่ในอาร์เรย์ค่าทั้งหมดด้วยการทดสอบหน่วย อย่างไรก็ตามฉันยังคงเห็นบางคนเพิ่มองค์ประกอบเพิ่มเติม แต่ไม่บังคับใช้ในการทดสอบหน่วยซึ่งยังทำให้เรากลับมาที่จุดเริ่มต้นไม่ทราบว่าค่า enum ทั้งหมดจะถูกเก็บไว้ในค่าทั้งหมด
DonnaLea

278

ฉันสร้างฟังก์ชั่นยูทิลิตี้iterateEnum()สำหรับทำซ้ำเคสสำหรับenumประเภทที่กำหนดเอง

นี่คือตัวอย่างการใช้งาน:

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

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

ผลลัพธ์ใด:

♠
♥
♦
♣

แต่นี่ใช้สำหรับการดีบักหรือการทดสอบเท่านั้น : ขึ้นอยู่กับพฤติกรรมของคอมไพเลอร์ Swift1.1 ที่ไม่ได้บันทึกไว้ดังนั้นให้ใช้ความเสี่ยงของคุณเอง

นี่คือรหัส:

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

แนวคิดพื้นฐานคือ:

  • การแสดงหน่วยความจำของenumไม่รวมenums ที่มีประเภทที่เกี่ยวข้องเป็นเพียงดัชนีของกรณีเมื่อจำนวนคดีเป็น2...256เท่ากันUInt8เมื่อ257...65536มันUInt16และอื่น ๆ ดังนั้นมันอาจมาunsafeBitcastจากประเภทจำนวนเต็มที่ไม่ได้ลงนามที่สอดคล้องกัน
  • .hashValue ของค่า enum เหมือนกับดัชนีของเคส
  • .hashValueของ enum ค่า bitcasted จากที่ไม่ถูกต้อง0ดัชนี

แก้ไขสำหรับ Swift2 และนำแนวคิดการคัดเลือกมาใช้จากคำตอบของ @ 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
    }
}

แก้ไขสำหรับ 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
    }
}

แก้ไขสำหรับ 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
สุดยอดและเป็นคำตอบเดียวที่ตอบคำถาม! แต่ใช่ ... ไม่ต้องแตะต้อง! +1 สำหรับความพยายามแม้ว่า!
dotToString

ฉันเพิ่งโพสต์คำตอบของฉันที่ทำงานโดยทั่วไปในลักษณะเดียวกัน (เห็นคำตอบนี้ในภายหลังเท่านั้น) มันใช้ Swift 2.0 Beta 6 และคุณสมบัติที่ทันสมัยของภาษา
Kametrixom

2
Swift 3 เวอร์ชั่นใช้งานได้ดี จำเป็นต้องปรับเปลี่ยนการใช้งานเล็กน้อย: สำหรับ f ใน iterateEnum (Suit.self) {print (f.rawValue)}
Gene De Lisa

16
+1 นี่ยอดเยี่ยมมาก นอกจากนี้ยังเป็น IMHO ที่ฉลาดเกินกว่าที่จะใช้งานซึ่งพิสูจน์ได้จากการแตกหักอย่างมีนัยสำคัญในการเปลี่ยนแปลงรุ่น Swift ทุกรุ่น เครดิตของผู้เขียนรุ่น Swift 3 ได้ทำไปหนึ่งเดือนก่อนที่ Swift 3 จะออกมาจากเบต้า ... หากคุณจะตอบคำถามนี้และเรียนรู้สิ่งนี้withUnsafePointer withMemoryReboundและpointeeสิ่งต่าง ๆ ทั้งหมดให้ใช้สิ่งนี้ทุกวิถีทาง มิฉะนั้นฉันจะหลีกเลี่ยง
Dan Rosenstark

5
ฉันแค่อยากจะเพิ่มสิ่งนี้ตอนนี้แตกใน swift 4 แต่เฉพาะใน linux ดังนั้น +1 ความคิดเห็นข้างต้นว่านี่ฉลาดเกินกว่าจะใช้
Andrew Plummer

130

โซลูชันอื่น ๆ ใช้งานได้แต่พวกเขาทั้งหมดตั้งสมมติฐานเช่นจำนวนตำแหน่งและชุดที่เป็นไปได้หรืออาจเป็นอันดับแรกและอันดับสุดท้าย จริงรูปแบบของสำรับไพ่อาจจะไม่เปลี่ยนแปลงมากนักในอนาคตอันใกล้ โดยทั่วไปแล้วมันเป็นสิ่งที่ดีกว่าที่จะเขียนโค้ดซึ่งทำให้สมมติฐานน้อยที่สุด ทางออกของฉัน:

ฉันได้เพิ่มประเภท raw ลงในSuitenum ดังนั้นฉันสามารถใช้Suit(rawValue:)เพื่อเข้าถึงSuitเคส:

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

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

ด้านล่างการนำcreateDeck()วิธีการของบัตรมาใช้ init(rawValue:)เป็นตัวเริ่มต้นพร้อมใช้งานและส่งกลับเป็นตัวเลือก โดยการแกะและตรวจสอบค่าของมันทั้งในขณะที่ statement ไม่จำเป็นต้องสมมติจำนวนRankหรือSuitกรณี:

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

นี่คือวิธีการเรียกใช้createDeckเมธอด:

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

12
คำตอบที่ดีที่สุดแน่นอนฉันเคยเห็นหัวข้อต่างๆในหัวข้อนี้ สง่างามมาก. ใช้งานได้กับการระบุประเภท Int แต่ฉันสงสัยว่าจะวนซ้ำผ่านประเภทอื่น ๆ (สตริงประเภทที่กำหนดเอง ฯลฯ ) ได้อย่างไร
Jay Imerman

3
นี่เป็นทางออกที่ดีที่สุดอย่างแน่นอน สิ่งหนึ่งที่ควรทราบ ในตัวอย่างในหนังสือเล่มนี้ไม่มี "case Spades = 1" ตามที่ sdduursma มี ฉันไม่ได้จับมันในตอนแรก นั่นคือหนึ่งตัวเลือกหรือคุณสามารถใช้ "var m = 0"
Joshua Goossen

10
นี่ทำให้สมมติฐานที่ว่าค่าดิบเป็นแบบลำดับ เมื่อสิ่งนี้ไม่เป็นจริงเช่นเมื่อ enum แสดงถึงแฟล็กรูปแบบบิตการวนซ้ำจะออกก่อนเวลา
จิม

1
Suitวิธีการแก้ปัญหานี้จะถือว่าคุณสามารถปรับเปลี่ยนความหมายของ ในตัวอย่างนี้คุณสามารถทำได้ แต่การฝึกซ้อมมีไว้เพื่อให้คุณทำงานกับสิ่งที่enumsคุณได้รับราวกับว่ามาจากแหล่งภายนอก
Josh J

1
สิ่งเดียวที่ฉันต้องการบ่นคือฉันไม่สามารถเรียกมันว่าเป็นวิธีการคงที่ แต่ต้องสร้างวัตถุการ์ดก่อน
qed

76

ฉันสะดุดในบิตและไบต์และสร้างส่วนขยายที่ฉันพบภายหลังทำงานคล้ายกับคำตอบของ@rintaro มันใช้แบบนี้:

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

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

สิ่งที่น่าสังเกตคือมันสามารถใช้กับ enum ใด ๆ โดยไม่มีค่าที่เกี่ยวข้อง โปรดทราบว่าวิธีนี้ใช้ไม่ได้กับ enums ที่ไม่มีกรณี

เช่นเดียวกับคำตอบของ@rintaroรหัสนี้ใช้การเป็นตัวแทนของ enum การเป็นตัวแทนนี้ไม่ได้จัดทำเป็นเอกสารและอาจมีการเปลี่ยนแปลงในอนาคตซึ่งอาจทำลายได้ ฉันไม่แนะนำให้ใช้สิ่งนี้ในการผลิต

รหัส (Swift 2.2, Xcode 7.3.1, ไม่ทำงานบน 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
            }
        }
    }
}

รหัส (Swift 3, Xcode 8.1, ไม่ทำงานบน 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
            }
        }
    }
}

ฉันไม่รู้ว่าทำไมฉันต้องการtypealiasแต่คอมไพเลอร์บ่นโดยไม่ต้องมัน


10
คำตอบนี้ดียิ่งขึ้นกว่าคำตอบของฉันโดยเฉพาะอย่างยิ่งในส่วนที่หล่อ :)
Rintaro

แต่ฉันคิดว่ามันใช้งานได้กับสภาพแวดล้อมของ endian น้อยหรือไม่
rintaro

5
Xcode 8 beta 6 ได้เปลี่ยนแปลงสิ่งนี้อีกครั้ง! ฉันได้รับข้อผิดพลาดต่อไปนี้ `'init' ไม่สามารถใช้งานได้: ใช้ 'withMemoryRebound (ถึง: ความจุ: _)' เพื่อดูหน่วยความจำชั่วคราวเป็นรูปแบบที่รองรับรูปแบบอื่นได้ชั่วคราว '
Vorlon

1
@ConfusedVorlon: ดูคำตอบข้างบนโดย @Rintaro: แทนที่withUnsafePointerpointee}โดยwithUnsafePointer(to: &i) { $0.withMemoryRebound(to: T.self, capacity: 1) { $0.pointee } }
Stefan

1
ดูเหมือนว่าจะไม่ทำงานในฐานะของ Xcode 10 อีกต่อไป (ฉันรู้ว่านี่ไม่จำเป็นต้องใช้กับ Swift 4.2) แต่เมื่อใช้ Swift 4 (.1) ใน Xcode 10 รหัสนี้ไม่ทำงานอีกต่อไป (ค่าดิบไม่เท่ากัน)
benrudhart

26

คุณสามารถวนซ้ำผ่าน enum โดยใช้ForwardIndexTypeโปรโตคอล

ForwardIndexTypeโปรโตคอลคุณจะต้องกำหนดsuccessor()ฟังก์ชั่นขั้นตอนผ่านองค์ประกอบ

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

การวนซ้ำในช่วงเปิดหรือปิด ( ..<หรือ...) จะเรียกใช้successor()ฟังก์ชันภายในซึ่งช่วยให้คุณสามารถเขียนสิ่งนี้:

// 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
ฉันคิดว่านี่เป็นคำตอบที่ "ถูกต้อง" ที่สุดแม้ที่สุด "สง่างาม" (จำเป็นต้องใช้รหัสเพิ่มเติมต้องใช้ตัวเลือกอื่นที่นี่) ให้ซินแทกซ์ผลลัพธ์เมื่อใช้ภายในช่วง (ไวยากรณ์คือสิ่งที่ฉัน จะคาดหวังว่าจะสามารถทำได้ถ้าหากนับตั้งแต่ที่มีการแก้ปัญหานับไม่ถ้วน) ขอบคุณ! แม้ว่าจะเป็นที่น่าสังเกตว่าหากผู้ปฏิบัติงานมีการบรรทุกเกินพิกัดในที่อื่นนอกเหนือจากตัวตายตัวแทน () (ซึ่งดูเหมือนว่าน่าดึงดูด) จากนั้นเห็นได้ชัดว่าการบังคับแกะเป็นอันตราย นอกจากนี้มัดดูเหมือนว่าไม่จำเป็น ... ?
iOS Gamer

คำตอบที่อัปเดตแล้วเพื่อสะท้อนถึงข้อกำหนดภาษา Swift ล่าสุด
RndmTsk

successor()วิธีการที่กำหนดไว้อย่างถูกต้อง(ตัวเลือกแรก) จะขจัดความต้องการที่enumจะมีประเภทที่เกี่ยวข้อง +1
เกิน

1
แต่คำตอบที่สวยงามนี้จะไม่ทำงานกับ String enums ใช่ไหม
Ali

ส่วนใหญ่ "เหมาะสม" / วิธีปฏิบัติที่ดีที่สุด! + 1-ed
งุดชิงโปง - อะซึกะเคนจิ -

18

ปัญหานี้ตอนนี้ง่ายขึ้นมาก นี่คือโซลูชัน 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
}

ก่อน 4.2:

ฉันชอบวิธีนี้ที่ฉันรวบรวมหลังจากค้นหา " List comprehension in Swift "

มันใช้ Int raws แทน Strings แต่หลีกเลี่ยงการพิมพ์สองครั้งจะอนุญาตให้กำหนดช่วงและไม่ยากค่าดิบรหัส

นี่เป็นโซลูชันดั้งเดิมของ Swift 4 แต่ดูการปรับปรุง 4.2 ด้านบน:

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
}

โอ้ตอนนี้ฉันเห็นแล้วว่าของฉันนั้นเหมือนกับของ Sutean Rutjanalard
adazacom

1
ที่จริงแล้วฉันชอบการนำไปใช้ของคุณดีกว่า ฉันคิดว่ามันชัดเจนกว่า! 1 โหวต ที่จริงแล้วคำตอบที่โหวตมากที่สุดนั้นฉลาดเกินไปและแน่นอนที่สุดจะแตกหักในอนาคต คุณสัญญาว่าจะมีความมั่นคงในอนาคต
jvarela

17

ในหลักการเป็นไปได้ที่จะทำเช่นนี้โดยสมมติว่าคุณไม่ได้ใช้การกำหนดค่าดิบสำหรับกรณีของ 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
นี่เป็นสิ่งที่ดี แต่ใช้ได้สำหรับ Integer อย่างต่อเนื่องเริ่มต้นที่ 0
Robert

@Robert ตามที่ความคิดเห็นของฉันระบุไว้ด้านบน: "คุณไม่ได้ใช้การกำหนดค่าดิบสำหรับกรณีของ enum"
Alfa07

ใช่ - อย่าใช้ค่าดิบเช่นเดียวกับการตั้งค่าประเภทพื้นฐานให้เป็น int ในรวดเร็วคุณไม่จำเป็นต้องมีประเภทสำหรับ enum เหมือนในตัวอย่างชุดสูท enum ItWontWorkForThisEnum {case a, b, c}
Robert

สิ่งนี้จะแก้ไขปัญหาได้อย่างไรหาก tuple เชื่อมโยงกับกรณีการแจงนับ
rougeExciter

คุณไม่สามารถเชื่อมโยง tuple กับ enum ได้อย่างง่ายดาย
nhgrif

13

ถ้าคุณให้ค่า enum ดิบกับ Intumมันจะทำให้การวนซ้ำง่ายขึ้นมาก

ตัวอย่างเช่นคุณสามารถใช้anyGeneratorเพื่อรับตัวสร้างที่สามารถระบุค่าของคุณ:

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

อย่างไรก็ตามนี่ดูเหมือนว่าเป็นรูปแบบที่ค่อนข้างธรรมดามันจะดีถ้าเราสามารถสร้าง enum ประเภทใด ๆ ได้โดยเพียงแค่ทำตามโปรโตคอลหรือไม่? ด้วย Swift 2.0 และส่วนขยายโปรโตคอลตอนนี้เราทำได้แล้ว!

เพียงเพิ่มลงในโครงการของคุณ:

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

ตอนนี้เมื่อใดก็ตามที่คุณสร้าง enum (ตราบใดที่มีค่าดิบ) คุณสามารถทำให้นับได้โดยสอดคล้องกับโปรโตคอล:

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

หากค่า enum ของคุณไม่ได้ขึ้นต้นด้วย0(ค่าเริ่มต้น) ให้แทนที่firstRawValueเมธอด:

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

คลาส Suit สุดท้ายรวมถึงการแทนที่simpleDescriptionด้วยโปรโตคอล CustomStringConvertible ที่เป็นมาตรฐานมากขึ้นจะมีลักษณะดังนี้:

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

ไวยากรณ์ของ 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 ++ จะถูกลบออกอย่างรวดเร็ว 3. คุณแนะนำอะไรให้แทนที่ - var nextIndex = firstRawValue () ส่งคืน anyGenerator {Self (rawValue: nextIndex ++)}
Avi

คิดออก defer {nextIndex + = 1} คืน AnyGenerator {Self (rawValue: nextIndex)}
Avi

12

อัปเดตเป็น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
        }
    }
}

มันได้รับการอัพเดทโค้ดให้เป็น Swift 2.2 form @ คำตอบของ Kametrixom

สำหรับSwift 3.0+ (ขอบคุณ@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 คุณช่วยอธิบายความหมายของคุณได้อย่างไร
ale_stro

อ๊ะขอโทษฉันตรวจสอบอีกครั้งและมีข้อผิดพลาดในสนามเด็กเล่น: ในโครงการจริงรหัสนี้ใช้งานได้ตามที่คาดหวังขอบคุณ! =)
silvansky

1
สุดยอดทางออก! ทำงานได้ดีบน Swift 3 พร้อมการเปลี่ยนแปลงเล็กน้อย ('AnyGenerator' เปลี่ยนชื่อเป็น 'AnyIterator' และ '.memory' เปลี่ยนชื่อเป็น '.pointee')
ฟิลิป

9

โซลูชัน 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

ฉันพบว่าตัวเองกำลังทำอะไร.allValuesมากมายตลอดทั้งรหัส ในที่สุดฉันก็พบวิธีที่จะเพียงแค่ปฏิบัติตามIteratableโปรโตคอลและมีrawValues()วิธีการ

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 พร้อม 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 }
}

เรียกมันว่า

print(Filter.allValues)

พิมพ์:

["เงินเดือน", "ประสบการณ์", "เทคโนโลยี", "Unutilized", "Unutilized มูลค่าสูง"]


รุ่นเก่ากว่า

สำหรับการenumเป็นตัวแทนInt

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

เรียกว่าเป็นแบบนี้:

print(Filter.allValues)

พิมพ์:

[0, 1, 2, 3, 4]


สำหรับการenumเป็นตัวแทนString

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

เรียกมันว่า

print(Filter.allValues)

พิมพ์:

["เงินเดือน", "ประสบการณ์", "เทคโนโลยี", "Unutilized", "Unutilized มูลค่าสูง"]


7

แก้ไข: Swift Evolution Proposal SE-0194 คอลเลกชันที่ได้รับมาจากเคส Enumเสนอวิธีการแก้ปัญหาระดับหัวในการแก้ไขปัญหานี้ เราเห็นมันใน Swift 4.2 และใหม่กว่า ข้อเสนอยังชี้ให้เห็นถึงวิธีการแก้ไขปัญหาบางอย่างที่คล้ายกับที่กล่าวถึงแล้ว แต่อาจเป็นเรื่องที่น่าสนใจ

ฉันจะเก็บโพสต์ดั้งเดิมของฉันไว้ด้วยเพื่อความสมบูรณ์


นี้ยังอีกวิธีหนึ่งขึ้นอยู่กับคำตอบ @ Peymmankh ของปรับให้เข้ากับสวิฟท์ 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
    }
}

แล้วเรื่องนี้ล่ะ


ขอบคุณมันใช้งานได้ตามที่ฉันต้องการ แต่มีโอกาสในการปิดแผนที่เพื่อรับค่าไม่ใช่ดัชนี แต่ตามชื่อ?
มิคาอิล

4

คุณสามารถลองแจกแจงแบบนี้

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
นั่นเป็นรหัสที่ไร้ประโยชน์มากมาย! มันเทียบเท่ากับการstatic var enumerate = [Mercury, Venus, Earth, Mars]ทำให้มันเป็นคำตอบ subpar ที่สุดเมื่อเทียบกับการโหวตคำตอบstackoverflow.com/a/24137319/1033581
Cœur

@Cur คำตอบนี้มีประโยชน์ที่สำคัญในการใช้คอมไพเลอร์เพื่อรับประกันว่าคุณจะไม่พลาดในทุกกรณี
dchakarov

@ Cœurที่มีปัญหาเดียวกันในการช่วยให้คุณที่จะทำให้เกิดข้อผิดพลาดของผู้ใช้เช่นคอมไพเลอร์จะไม่บ่นถ้าคุณเขียนreturn [Mercury, Venus, Mars]แทนreturn [Mercury, Venus, Earth, Mars]
dchakarov

@dchakarov ฉันตัดสินใจที่จะโพสต์การปรับปรุงเป็นคำตอบเพื่อความชัดเจน: stackoverflow.com/a/50409525/1033581
Cœur

@ Cœurหากในคำตอบใหม่ของคุณคุณแทนที่คำสั่ง return ด้วยreturn [.spades, .hearts, .clubs]คอมไพเลอร์จะไม่พูดอะไรแล้วเมื่อคุณลองใช้มันในโค้ดคุณจะได้รับ[TestApp.Suit.spades, TestApp.Suit.hearts, TestApp.Suit.clubs]- นั่นคือประเด็นของฉัน - ว่าถ้าคุณกำลังรับมือกับปัญหาใหญ่ enum และคุณต้องเพิ่มหรือลบเคสเป็นครั้งคราวโซลูชันของคุณมีแนวโน้มที่จะเกิดข้อผิดพลาดจากการละเลยในขณะที่คำตอบปัจจุบัน แต่ไม่รัดกุมจะปลอดภัยกว่า
dchakarov

4

ใน Swift 3 เมื่อ enum พื้นฐานrawValueคุณสามารถใช้Strideableโปรโตคอลได้ ข้อดีคือไม่มีการสร้างอาร์เรย์ของค่าเหมือนในคำแนะนำอื่น ๆ และมาตรฐาน Swift "สำหรับใน" การทำงานแบบวนซ้ำซึ่งทำให้ไวยากรณ์ดี

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

อ่าสิ่งที่ฉันกำลังมองหาเพื่อแทนที่ ForwardIndexType ตอนนี้การทำซ้ำของฉันดูดีที่ไซต์การใช้งาน ... เพียงแค่วิธี Swifty ที่เหมาะสม
Andrew Duncan

4

วิธีการแก้ปัญหานี้สร้างสมดุลระหว่างความสามารถในการอ่านและบำรุงรักษา

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

ในความคิดของฉันนี่เป็นทางออกที่ดีที่สุด เมื่อฉันเห็นรหัส swift ส่วนใหญ่ความสามารถในการอ่านไม่ดีไปกว่า objc แต่อาจเป็นไปได้หากโปรแกรมเมอร์ให้ความสำคัญกับผู้อ่านรหัสมากขึ้น ตัวของพวกเขาในอนาคตเช่น :)
Vilém Kurz

4

ขออภัยคำตอบของฉันเฉพาะเจาะจงกับวิธีที่ฉันใช้โพสต์นี้ในสิ่งที่ฉันต้องทำ สำหรับผู้ที่สะดุดกับคำถามนี้มองหาวิธีที่จะค้นหากรณีใน Enum นี่คือวิธีที่จะทำ (ใหม่ใน Swift 2):

แก้ไข: camelCase ตัวพิมพ์เล็กตอนนี้เป็นมาตรฐานสำหรับค่า Swift 3 enum

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

สำหรับผู้ที่สงสัยเกี่ยวกับการแจงนับบน enum คำตอบที่ให้ไว้ในหน้านี้ที่มี var / let แบบคงที่ที่มีอาร์เรย์ของค่า enum ทั้งหมดนั้นถูกต้อง โค้ดตัวอย่างล่าสุดของ Apple สำหรับ tvOS มีเทคนิคเดียวกันนี้

ดังที่กล่าวมาพวกเขาควรสร้างกลไกที่สะดวกขึ้นในภาษานั้น (Apple คุณฟังอยู่หรือเปล่า?)!


3

การทดลองคือ: EXPERIMENT

เพิ่มวิธีการลงในการ์ดที่สร้างสำรับไพ่เต็มใบโดยมีไพ่หนึ่งใบในแต่ละชุดของอันดับและสูท

ดังนั้นหากไม่มีการแก้ไขหรือปรับปรุงโค้ดที่ให้นอกเหนือจากการเพิ่มวิธีการ (และโดยไม่ใช้สิ่งที่ยังไม่ได้รับการสอน) ฉันจึงได้คำตอบนี้:

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

นี่คือวิธีที่ฉันใช้ในการวนซ้ำenumและให้ค่าหลายประเภทจากหนึ่งenum

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

นี่คือผลลัพธ์:

The Number Zero in french: zéro, spanish: cero, ญี่ปุ่น: nuru
The number One in French : un, สเปน: uno, ญี่ปุ่น: ichi
The number Two in French: deux, สเปน: dos, ญี่ปุ่น: ni
The Number Three in french : trois, สเปน: tres, ญี่ปุ่น: san
The number Four in French: quatre, สเปน: cuatro, ญี่ปุ่น: shi
The number Five in French: cinq, สเปน: cinco, ญี่ปุ่น: go
The Six Six ในภาษาฝรั่งเศส: หก, สเปน: seis, ญี่ปุ่น: roku
จำนวนเจ็ดในฝรั่งเศส: sept, สเปน: siete, ญี่ปุ่น: shichi

รวม 8 ราย

siete ในภาษาญี่ปุ่น: shichi


UPDATE

ฉันเพิ่งสร้างโปรโตคอลเพื่อจัดการการแจงนับ โปรโตคอลต้องการ enum ที่มีค่า raw แบบดิบ:

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

ดูเหมือนว่าแฮ็ค แต่ถ้าคุณใช้ค่าดิบคุณสามารถทำสิ่งนี้ได้

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

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

2

ในขณะที่จัดการกับSwift 2.0นี่คือข้อเสนอแนะของฉัน:

ฉันได้เพิ่มประเภท raw ไว้ใน Suit enum

enum Suit: Int {

แล้ว:

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

เช่นเดียวกับ @Kametrixom ตอบที่นี่ฉันเชื่อว่าการคืนค่าอาร์เรย์จะดีกว่าการส่งกลับ AnySequence เนื่องจากคุณสามารถเข้าถึงสารพัดของ Array ทั้งหมดเช่นการนับเป็นต้น

นี่คือการเขียนซ้ำ:

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

ทางออกอื่น:

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

นี่เป็นโพสต์เก่าที่ค่อนข้างดีจาก Swift 2.0 ขณะนี้มีโซลูชันที่ดีกว่าที่นี่ซึ่งใช้คุณลักษณะใหม่ของ swift 3.0: การวน ซ้ำผ่าน Enum ใน Swift 3.0

และในคำถามนี้มีทางออกที่ใช้คุณสมบัติใหม่ของ (ที่ยังไม่เผยแพร่เมื่อฉันเขียนการแก้ไขนี้) Swift 4.2: ฉันจะรับจำนวน Swift enum ได้อย่างไร


มีวิธีแก้ปัญหาที่ดีมากมายในกระทู้นี้และอื่น ๆ แต่บางอันก็ซับซ้อนมาก ฉันชอบที่จะลดความซับซ้อนให้มากที่สุด นี่เป็นวิธีแก้ปัญหาที่อาจหรืออาจจะไม่ทำงานสำหรับความต้องการที่แตกต่างกัน แต่ฉันคิดว่ามันใช้งานได้ดีในกรณีส่วนใหญ่:

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

ในการทำซ้ำ:

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

1
ที่ให้ความรู้สึกเหมือนเป็นงานที่เฉพาะเจาะจงสำหรับแต่ละ enum ที่คุณสร้างขึ้น - ฉันไม่แน่ใจว่าการส่งคืน [Number.One.rawValue, Number.Two.rawValue, ... ] นั้นไม่สะอาดในกรณีนี้ .
Scott Austin

นี่เป็นโพสต์เก่าที่ค่อนข้างดีจาก Swift 2.0 ตอนนี้มีโซลูชันที่ดีกว่าที่นี่ซึ่งใช้ฟีเจอร์ใหม่ของ swift 3.0: stackoverflow.com/questions/41352594/ และสำหรับคำถามนี้มีวิธีแก้ปัญหาที่ใช้ฟีเจอร์ใหม่ของ (ยังไม่ออกเมื่อฉันเขียนแก้ไขนี้ ) Swift 4.2: stackoverflow.com/questions/27094878/…
Abbey Jackson

2

Enums มีtoRaw()และfromRaw()วิธีการ ดังนั้นหากมูลค่าดิบของคุณคือIntคุณสามารถทำซ้ำได้ตั้งแต่แรกถึงครั้งสุดท้าย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()
    }
}

หนึ่ง gotcha คือคุณต้องทดสอบค่าที่เป็นตัวเลือกก่อนที่จะเรียกใช้simpleDescriptionเมธอดดังนั้นเราจึงตั้งconvertedSuitค่าเป็นค่าของเราก่อนแล้วตั้งค่าคงที่เป็นconvertedSuit.simpleDescription()


2
คำถามเดิมเกี่ยวกับประเภทของสตริง enum ไม่ใช่ Int
cynistersix

2

นี่คือวิธีการแนะนำของฉัน ไม่เป็นที่น่าพอใจอย่างสมบูรณ์ (ฉันใหม่กับ Swift และ OOP!) แต่อาจมีบางคนที่สามารถปรับปรุงได้ ความคิดคือการให้แต่ละ enum ให้ข้อมูลช่วงของตัวเองเป็น.firstและ.lastคุณสมบัติ มันเพิ่มโค้ดเพียงสองบรรทัดในแต่ละ enum: ยังคงเป็นรหัสฮาร์ดบิต แต่อย่างน้อยก็ไม่ได้ทำซ้ำทั้งชุด มันไม่จำเป็นต้องปรับเปลี่ยนSuitenum ให้เป็น Int เช่นRank enum แทนการยกเลิกการพิมพ์

แทนที่จะแสดงวิธีแก้ปัญหาทั้งหมดนี่คือรหัสที่ฉันเพิ่มใน.enum ที่ไหนสักแห่งหลังจากคำสั่ง case ( Suitenum คล้ายกัน):

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

และลูปที่ฉันใช้เพื่อสร้างเด็คเป็นอาร์เรย์ของสตริง (คำจำกัดความของปัญหาไม่ได้ระบุว่าดาดฟ้ามีโครงสร้างอย่างไร)

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
}

มันไม่เป็นที่น่าพอใจเนื่องจากคุณสมบัตินั้นเชื่อมโยงกับองค์ประกอบแทนที่จะเป็น enum แต่มันเพิ่มความชัดเจนให้กับลูป 'for' ฉันอยากจะพูดRank.firstแทนRank.Ace.firstแทนมันใช้งานได้ (กับองค์ประกอบใด ๆ ) แต่มันน่าเกลียด ใครสามารถแสดงวิธีการยกระดับที่ enum?

และเพื่อให้มันทำงานได้ฉันได้ยกcreateDeckวิธีการออกจากโครงสร้างของการ์ด ฉันไม่สามารถหาวิธีรับอาร์เรย์ [String] ที่ส่งคืนจาก struct นั้นและดูเหมือนว่าจะเป็นวิธีที่ไม่ดีในการนำวิธีดังกล่าว


2

ฉันทำมันโดยใช้คุณสมบัติที่คำนวณได้ซึ่งส่งคืนอาร์เรย์ของค่าทั้งหมด (ขอบคุณโพสต์นี้http://natecook.com/blog/2014/10/loopy-random-enum-ideas/ ) อย่างไรก็ตามมันยังใช้ค่าดิบ int แต่ฉันไม่จำเป็นต้องทำซ้ำการแจงนับสมาชิกทั้งหมดในคุณสมบัติแยกต่างหาก

UPDATE Xcode 6.1 เปลี่ยนวิธีการใช้สมาชิก enum เล็กน้อยrawValueดังนั้นฉันจึงแก้ไขรายการ นอกจากนี้ยังมีการแก้ไขข้อผิดพลาดเล็ก ๆ 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
    }
}

ตามคำตอบของริค: เร็วกว่านี้ 5 เท่า


การเพิ่มCountเคสจะทำให้switchการใช้งานและการCaseIterableปฏิบัติตามข้อกำหนดครบถ้วนสมบูรณ์
Cœur
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.