Làm thế nào để liệt kê một enum với kiểu String?


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

Ví dụ: làm thế nào tôi có thể làm một cái gì đó như:

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

Ví dụ kết quả:



Trong trường hợp nào bạn sẽ không biết loại?
mtbennett

Bạn đã đúng, trong trường hợp này là kiểu Chuỗi.
Lucien

Chưa có phản ánh nào trong Swift ...
Sulthan

22
Không phải là mỉa mai khi họ được gọi là liệt kê, nhưng họ rất khó chịu khi liệt kê trong Swift?
Charlton Provatas

3
@CharltonProvatas Nếu đó là nhược điểm duy nhất trong Swift, tôi sẽ gọi nó là một ngày. Nhìn vào có bao nhiêu người đưa ra cách giải quyết khác nhau cho việc này, tôi chỉ đang gặm bàn phím.
qwerty_so

Câu trả lời:


267

Swift 4.2+

Bắt đầu với Swift 4.2 (với Xcode 10), chỉ cần thêm sự phù hợp giao thức CaseIterableđể hưởng lợi từ allCases. Để thêm sự phù hợp giao thức này, bạn chỉ cần viết ở đâu đó:

extension Suit: CaseIterable {}

Nếu enum là của riêng bạn, bạn có thể chỉ định sự phù hợp trực tiếp trong khai báo:

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

Sau đó, đoạn mã sau sẽ in tất cả các giá trị có thể:

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

Khả năng tương thích với các phiên bản Swift trước đó (3.x và 4.x)

Nếu bạn cần hỗ trợ Swift 3.x hoặc 4.0, bạn có thể bắt chước triển khai Swift 4.2 bằng cách thêm mã sau đây:

#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 Tôi rất sẵn lòng giúp đỡ, nhưng: (1) bạn có chắc là bạn đã có phiên bản mã mới nhất không? (một số sự cố đã được khắc phục một tháng trước) và (2) vui lòng cung cấp MCVE của loại tùy chỉnh có thể tái tạo sự cố và phiên bản Xcode của bạn.
Cœur

Điều này hoạt động tốt đối với tôi đối với các bản dựng gỡ lỗi, nhưng ngay khi tôi tạo một bản phát hành và tải lên TestFlight thì nó gặp sự cố. Có phải Apple bằng cách nào đó tước bỏ điều này?
Daniel Wood

1
Có vẻ như phiên bản của bạn có một điểm tích cực so với phiên bản CaseIterator sẵn có. Tôi có thể mở rộng các enum được xác định trong một tệp khác với phiên bản của bạn. Nếu bạn đặt allCase trong tiện ích mở rộng, bạn cũng có thể mở rộng các enum được xác định trong các khung khác nhau.
adamfowlerphoto

1
@CyberMew Tôi đã cập nhật câu trả lời để làm rõ nó. Cuốn sách Swift và ghi chú phát hành Xcode 10 đề cập đến CaseIterable là câu trả lời của tôi và họ đã sử dụng các ví dụ đơn giản trong đó enum không được hỗ trợ String, trái ngược với câu hỏi Stack Overflow hiện tại.
Cœur

1
Tôi muốn nhấn mạnh tầm quan trọng của "# if! Swift (> = 4.2)". Nếu bạn đã viết mã của mình trước swift 4.2 và bạn quên xóa mã dưới đây "# if! Swift (> = 4.2)", khi bạn sử dụng Xcode Phiên bản 11.4 để xây dựng cục bộ trên thiết bị thử nghiệm của mình, mọi thứ sẽ ổn. Nhưng khi ứng dụng của bạn được tải xuống từ cửa hàng ứng dụng hoặc chuyến bay thử nghiệm, đoạn mã đó sẽ làm hỏng ứng dụng của bạn. Đó là loại lỗi rất khó phát hiện hoặc gỡ lỗi.
Oliver Zhang

524

Bài đăng này có liên quan ở đây https://www.swift-studies.com/blog/2014/6/10/enumerating-enums-in-swift

Về cơ bản, giải pháp đề xuất là

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

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

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

188
Đẹp, nhưng ... bạn phải nhập các yếu tố của bảng liệt kê hai lần - một lần cho phép liệt kê, một lần cho allValues. Không chính xác thanh lịch như người ta mong muốn.
Jay Imerman

2
Đồng ý với "nhưng" ... tuy nhiên như đã nêu trong bài viết có lẽ có một vấn đề là một enum thực sự là một tập hợp và do đó không có thứ tự ... hãy nhớ rằng ... các trường hợp đặt hàng được xác định trong sẽ không phải là một khởi đầu tồi!
rougeExciter

3
Trong Java trình biên dịch thực hiện điều này cho bạn, có thể Swift 2.0 cũng sẽ làm điều này. Đặc biệt trong Java, tất cả các enum đều nhận được một phương thức mô tả (toString in Java) cung cấp Chuỗi như tên trường hợp (Washers, ...) và Tập hợp các trường hợp được tạo tự động. Java cũng cung cấp cho bạn lập chỉ mục vị trí. Như tôi đã nói, có lẽ Swift 2.0.
Howard Lovatt

1
Lý tưởng nhất là bạn sẽ có một cái gì đó tương tự như triển khai c # trong đó bạn có thể làm Enum.Values(typeof(FooEnum))nhưng tiếp xúc như một phương thức mở rộng (như bản đồ hoặc thu nhỏ). FooEnum.values() :: values(EnumType -> [EnumType])
Rodrigoelp

1
Bài viết làm cho một điểm tốt ngay ở phần cuối về việc làm cho mỗi giá trị enum nằm trong mảng allValues ​​với một bài kiểm tra đơn vị. Tuy nhiên, tôi vẫn có thể thấy ai đó thêm nhiều yếu tố, nhưng không thực thi chúng trong bài kiểm tra đơn vị vẫn khiến chúng tôi quay lại từ đầu, không biết chắc chắn rằng tất cả các giá trị enum được giữ trong allValues.
DonnaLea

278

Tôi đã thực hiện một chức năng tiện ích iterateEnum()cho các trường hợp lặp cho các enumloại tùy ý .

Dưới đây là cách sử dụng ví dụ:

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

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

Đầu ra nào:

♠
♥
♦
♣

Nhưng, điều này chỉ dành cho mục đích gỡ lỗi hoặc kiểm tra : Điều này phụ thuộc vào một số hành vi của trình biên dịch Swift1.1 không có giấy tờ, vì vậy, hãy tự chịu rủi ro khi sử dụng nó.

Đây là mã:

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

Ý tưởng cơ bản là:

  • Đại diện bộ nhớ của enum, ngoại trừ enums với các loại liên quan, chỉ là một chỉ số của các trường hợp khi số lượng các trường hợp là 2...256, nó giống hệt với UInt8, khi 257...65536, nó UInt16và vân vân. Vì vậy, nó có thể unsafeBitcasttừ các loại số nguyên không dấu tương ứng.
  • .hashValue giá trị enum giống như chỉ số của vụ án.
  • .hashValuegiá trị enum bitcasted từ chỉ mục không hợp lệ0.

Sửa đổi cho Swift2 và thực hiện các ý tưởng truyền từ câu trả lời của @ 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
    }
}

Sửa đổi cho 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
    }
}

Sửa đổi cho 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
Tuyệt vời và câu trả lời duy nhất trả lời câu hỏi! Nhưng vâng ... sẽ không chạm vào nó! +1 cho nỗ lực, mặc dù!
dotToString

Tôi chỉ đăng câu trả lời của tôi về cơ bản hoạt động theo cùng một cách (chỉ thấy câu trả lời này sau). Nó sử dụng Swift 2.0 beta 6 và các tính năng hiện đại của ngôn ngữ.
Kametrixom

2
Phiên bản Swift 3 hoạt động tốt. Chỉ cần sửa đổi cách sử dụng một chút: cho f trong iterateEnum (Suit.elf) {print (f.rawValue)}
Gene De Lisa

16
+1 này khá xuất sắc. IMHO cũng vậy, quá thông minh để sử dụng, bằng chứng là nó phá vỡ đáng kể trong mỗi thay đổi phiên bản Swift lớn. Theo tín dụng của tác giả, phiên bản Swift 3 đã được thực hiện một tháng trước khi Swift 3 ra bản beta ... Nếu bạn sẽ có câu trả lời này và tìm hiểu tất cả những thứ này withUnsafePointer withMemoryReboundvà những pointeethứ khác, thì hãy sử dụng nó bằng mọi cách. Nếu không, tôi sẽ tránh nó.
Dan Rosenstark

5
Tôi chỉ muốn thêm cái này bây giờ đã bị hỏng trong swift 4, nhưng chỉ trên linux, vì vậy +1 cho các ý kiến ​​trên rằng điều này quá thông minh để sử dụng.
Andrew Plummer

130

Các giải pháp khác hoạt động nhưng tất cả đều đưa ra các giả định về số lượng các cấp bậc và bộ quần áo có thể, hoặc thứ hạng đầu tiên và cuối cùng có thể là gì. Thật vậy, cách bố trí một cỗ bài có lẽ sẽ không thay đổi nhiều trong tương lai gần. Tuy nhiên, nói chung, việc viết mã sẽ càng ít giả định càng tốt. Giải pháp của tôi:

Tôi đã thêm một loại thô vào Suitenum, vì vậy tôi có thể sử dụng Suit(rawValue:)để truy cập các Suittrường hợp:

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

Dưới đây thực hiện createDeck()phương pháp của Thẻ . init(rawValue:)là một trình khởi tạo có sẵn và trả về một tùy chọn. Bằng cách hủy kết nối và kiểm tra giá trị của nó trong cả hai câu lệnh while, không cần phải thừa nhận số lượng RankhoặcSuit trường hợp:

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

Đây là cách gọi createDeckphương thức:

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

12
Câu trả lời tuyệt đối TỐT NHẤT tôi đã thấy trên các chủ đề khác nhau về chủ đề này. Rất thanh lịch. Điều này hoạt động với bảng liệt kê kiểu Int, nhưng tôi tự hỏi làm thế nào người ta có thể lặp qua các loại khác (chuỗi, loại tùy chỉnh, v.v.).
Jay Imerman

3
Đây chắc chắn là giải pháp tốt nhất. Một điều cần lưu ý. Trong ví dụ trong cuốn sách, nó không có "case Spades = 1" như sdduursma có. Tôi đã không bắt được điều này lúc đầu. đó là một tùy chọn hoặc bạn chỉ có thể sử dụng "var m = 0"
Joshua Goossen

10
Điều này làm cho giả định rằng các giá trị thô là tuần tự. Khi điều này không đúng, ví dụ khi enum biểu thị các cờ mặt nạ bit, vòng lặp sẽ thoát sớm.
Jim

1
Giải pháp này giả định rằng bạn có thể sửa đổi định nghĩa của Suit. Bạn có thể trong ví dụ này, nhưng bài tập có nghĩa là để bạn làm việc với enumsbạn đã được đưa ra như thể họ đến từ một nguồn bên ngoài.
Josh J

1
Điều duy nhất tôi muốn phàn nàn là tôi không thể gọi nó là một phương thức tĩnh, nhưng trước tiên phải tạo một đối tượng thẻ.
qed

76

Tôi tình cờ tìm thấy các bit và byte và tạo ra một phần mở rộng mà sau đó tôi phát hiện ra hoạt động rất giống với câu trả lời của @rintaro . Nó được sử dụng như thế này:

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

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

Điều đáng chú ý là nó có thể sử dụng trên bất kỳ enum nào mà không có giá trị liên quan. Lưu ý rằng điều này không làm việc cho enums không có trường hợp.

Như với câu trả lời của @rintaro , mã này sử dụng biểu diễn cơ bản của một enum. Đại diện này không được ghi nhận và có thể thay đổi trong tương lai, điều này sẽ phá vỡ nó. Tôi không khuyên bạn nên sử dụng điều này trong sản xuất.

Mã (Swift 2.2, Xcode 7.3.1, không hoạt động trên 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
            }
        }
    }
}

Mã (Swift 3, Xcode 8.1, không hoạt động trên 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
            }
        }
    }
}

Tôi không biết tại sao tôi cần typealias, nhưng trình biên dịch phàn nàn mà không có nó.


10
Câu trả lời này thậm chí còn tốt hơn câu trả lời của tôi, đặc biệt là trong phần casting :)
rintaro

Nhưng tôi nghĩ rằng điều này chỉ hoạt động trên môi trường endian nhỏ?
rintaro

5
Xcode 8 beta 6 đã thay đổi điều này một lần nữa! Tôi nhận được lỗi sau ` 'init' không có sẵn: sử dụng 'withMemoryRebound (để: Công suất: _)' để tạm thời xem như bộ nhớ khác type.` bố trí tương thích
Nhầm lẫn Vorlon

1
@ConfusedVorlon: xem câu trả lời ở trên bởi @Rintaro: thay thế withUnsafePointer... pointee}bởiwithUnsafePointer(to: &i) { $0.withMemoryRebound(to: T.self, capacity: 1) { $0.pointee } }
Stefan

1
Điều này dường như không còn hoạt động kể từ Xcode 10. (Tôi biết điều này sẽ không bắt buộc với Swift 4.2) nhưng khi sử dụng Swift 4 (.1) trong Xcode 10, mã này không còn hoạt động (giá trị thô không bằng nhau)
benrudhart

26

Bạn có thể lặp lại thông qua một enum bằng cách thực hiện ForwardIndexTypegiao thức.

Các ForwardIndexTypegiao thức đòi hỏi bạn phải xác định một successor()chức năng để bước qua các yếu tố.

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

Lặp lại trong phạm vi mở hoặc đóng ( ..<hoặc ...) sẽ gọi bên trong successor()hàm cho phép bạn viết điều này:

// 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
Tôi thấy đây là câu trả lời "phù hợp" nhất cho câu hỏi, thậm chí là "thanh lịch" nhất (mã bổ sung cần thiết cho các tùy chọn khác ở đây) với cú pháp kết quả khi được sử dụng trong phạm vi (cú pháp là những gì tôi sẽ mong đợi có thể làm được nếu enum nơi vô số cách giải quyết sans). Cảm ơn! Mặc dù điều đáng chú ý là nếu quá tải toán tử được sử dụng ở nơi khác ngoài người kế nhiệm () (có vẻ hấp dẫn) thì rõ ràng việc hủy bỏ bắt buộc là nguy hiểm. Ngoài ra, phần tử có vẻ không cần thiết ...?
Game thủ iOS

Câu trả lời được cập nhật để phản ánh thông số kỹ thuật ngôn ngữ Swift mới nhất
RndmTsk

Một successor()phương thức được xác định đúng (tùy chọn đầu tiên) sẽ loại bỏ sự cần thiết phải enumcó một loại liên quan. +1
năm14

1
Nhưng câu trả lời tao nhã này sẽ không hiệu quả với String enums phải không?
Ali

Giải pháp thực hành "phù hợp" / tốt nhất! + 1-ed
Siu Ching Pong -Asuka Kenji-

18

Vấn đề này bây giờ dễ dàng hơn nhiều. Đây là giải pháp Swift 4.2 của tôi:

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
}

Trước 4.2:

Tôi thích giải pháp này mà tôi đã kết hợp lại sau khi tìm thấy " Danh sách hiểu trong Swift ".

Nó sử dụng Int raw thay vì String nhưng nó tránh gõ hai lần, nó cho phép tùy chỉnh các phạm vi và không làm cứng các giá trị thô.

Đây là phiên bản Swift 4 của giải pháp ban đầu của tôi nhưng hãy xem cải tiến 4.2 ở trên:

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
}

Ồ, tôi thấy bây giờ của tôi về cơ bản giống như của Sutean Rutjanalard.
adazacom

1
Trên thực tế, tôi thích thực hiện của bạn tốt hơn. Tôi nghĩ rằng nó là rõ ràng hơn! 1 upvote. Trên thực tế, các câu trả lời được bình chọn nhiều nhất là quá thông minh và chắc chắn sẽ phá vỡ trong tương lai. Bạn hứa hẹn một số ổn định trong tương lai.
jvarela

17

Về nguyên tắc, có thể thực hiện theo cách này với giả định rằng bạn không sử dụng gán giá trị thô cho các trường hợp của 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
Điều này là tốt nhưng nó chỉ hoạt động cho các enum Integer liên tục bắt đầu từ 0
Robert

@Robert, như nhận xét của tôi nêu trên: "bạn không sử dụng gán giá trị thô cho các trường hợp của enum"
Alfa07

Có - không sử dụng các giá trị thô, cũng như đặt loại cơ bản thành int. Trong swift bạn không cần một kiểu cho enum như trong ví dụ về bộ đồ. enum ItWontWorkForThisEnum {case a, b, c}
Robert

Làm thế nào điều này sẽ giải quyết vấn đề nếu một tuple được liên kết với trường hợp liệt kê?
rougeExciter

Bạn không thể liên kết một tuple với enum rất dễ dàng.
nhgrif

13

Nếu bạn cho enum một giá trị Int thô nó sẽ giúp việc lặp dễ dàng hơn nhiều.

Ví dụ: bạn có thể sử dụng anyGeneratorđể có được một trình tạo có thể liệt kê các giá trị của bạn:

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

Tuy nhiên, điều này trông giống như một mô hình khá phổ biến, sẽ không hay nếu chúng ta có thể làm cho bất kỳ kiểu liệt kê nào chỉ đơn giản là tuân thủ một giao thức? Vâng với Swift 2.0 và giao thức mở rộng, bây giờ chúng ta có thể!

Đơn giản chỉ cần thêm điều này vào dự án của bạn:

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

Bây giờ bất cứ khi nào bạn tạo một enum (miễn là nó có giá trị Int), bạn có thể làm cho nó có thể đếm được bằng cách tuân thủ giao thức:

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

Nếu giá trị enum của bạn không bắt đầu bằng 0(mặc định), hãy ghi đè firstRawValuephương thức:

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

Lớp Suit cuối cùng, bao gồm thay thế simpleDescriptionbằng giao thức CustomStringConvertible chuẩn hơn , sẽ trông như thế này:

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

Cú pháp 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
    }
}

next Index ++ sẽ bị xóa trong swift 3. Bạn có đề xuất gì thay thế cho - var next Index = firstRawValue () return anyGenerator {Self (rawValue: nextIndex ++)}
Avi

Tìm ra. defer {next Index + = 1} return AnyGenerator {Self (rawValue: next Index)}
Avi

12

Đã cập nhật lên 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
        }
    }
}

Đó là mã được cập nhật thành mẫu Swift 2.2 @ Kametrixom's answer

Dành cho Swift 3.0+ (cảm ơn @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 bạn có thể vui lòng giải thích ý của bạn là gì không?
ale_stro

Rất tiếc, tôi xin lỗi, tôi đã kiểm tra lại và có một lỗi sân chơi: trong dự án thực tế, mã này hoạt động như mong đợi, cảm ơn! =)
silvansky

1
Giải pháp tuyệt vời! Ngoài ra, hoạt động tốt trên Swift 3 với các thay đổi tối thiểu ('AnyGenerator' được đổi tên thành 'AnyIterator' và '.memory' được đổi tên thành '.pointee').
Philip

9

Giải pháp 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

Tôi thấy mình làm .allValuesrất nhiều trong suốt mã của tôi. Cuối cùng tôi đã tìm ra một cách đơn giản để tuân thủ một Iteratablegiao thức và có một rawValues()phương thức.

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 với 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 }
}

Gọi nó đi

print(Filter.allValues)

Bản in:

["Mức lương", "Kinh nghiệm", "Công nghệ", "Không sử dụng", "Giá trị cao chưa sử dụng"]


Các phiên bản cũ hơn

Để enumđại diệnInt

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

Gọi nó như thế này:

print(Filter.allValues)

Bản in:

[0, 1, 2, 3, 4]


Để enumđại diệnString

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

Gọi nó đi

print(Filter.allValues)

Bản in:

["Mức lương", "Kinh nghiệm", "Công nghệ", "Không sử dụng", "Giá trị cao chưa sử dụng"]


7

EDIT: Swift Evolution Đề xuất SE-0194 Bộ sưu tập các trường hợp Enum đề xuất một giải pháp đứng đầu cấp độ cho vấn đề này. Chúng tôi thấy nó trong Swift 4.2 và mới hơn. Đề xuất cũng chỉ ra một số cách giải quyết tương tự như một số đã được đề cập ở đây nhưng vẫn có thể thú vị khi xem.

Tôi cũng sẽ giữ bài viết gốc của mình cho đầy đủ.


Đây là một cách tiếp cận khác dựa trên câu trả lời của @ Peymmankh, được điều chỉnh theo 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
    }
}

Còn cái này thì sao?


Cảm ơn, nó hoạt động như tôi cần. Nhưng có bất kỳ cơ hội nào trong việc đóng bản đồ để lấy giá trị không phải bằng chỉ mục mà bằng tên?
Mikhail

4

Bạn có thể thử liệt kê như thế này

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
Đó là rất nhiều mã vô dụng! Nó tương đương với static var enumerate = [Mercury, Venus, Earth, Mars], làm cho nó trở thành một câu trả lời phụ so với hầu hết các câu trả lời được bình chọn stackoverflow.com/a/24137319/1033581
Cur

@ Câu trả lời này có lợi ích quan trọng của việc sử dụng trình biên dịch để đảm bảo bạn sẽ không bỏ lỡ trường hợp nào.
dchakarov

@ Cur có cùng một vấn đề là cho phép bạn gây ra lỗi người dùng, tức là trình biên dịch sẽ không phàn nàn nếu bạn viết return [Mercury, Venus, Mars]thay vìreturn [Mercury, Venus, Earth, Mars]
dchakarov

@dchakarov Tôi quyết định đăng bài cải tiến như một câu trả lời, cho rõ ràng: stackoverflow.com/a/50409525/1033581
Cur

@ Cœur Nếu trong câu trả lời mới của bạn, bạn thay thế câu lệnh return bằng return [.spades, .hearts, .clubs]trình biên dịch này sẽ không nói điều gì và khi bạn cố gắng sử dụng nó trong mã, bạn sẽ nhận được [TestApp.Suit.spades, TestApp.Suit.hearts, TestApp.Suit.clubs]- đó là quan điểm của tôi - rằng nếu bạn đang xử lý một vấn đề lớn enum và bạn phải thêm hoặc loại bỏ các trường hợp theo thời gian, giải pháp của bạn dễ bị lỗi thiếu sót trong khi câu trả lời hiện tại, trong khi không súc tích, sẽ an toàn hơn.
dchakarov

4

Trong Swift 3, khi enum cơ bản có rawValue, bạn có thể thực hiện Strideablegiao thức. Ưu điểm là không có mảng giá trị nào được tạo như trong một số đề xuất khác và vòng lặp Swift "for in" tiêu chuẩn hoạt động, tạo ra một cú pháp hay.

// "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, chỉ là những gì tôi đang tìm kiếm để thay thế ForwardIndexType. Bây giờ các lần lặp của tôi trông tốt ở trang sử dụng ... chỉ là cách Swifty thích hợp.
Andrew Duncan

4

Giải pháp này đạt được sự cân bằng phù hợp về khả năng đọc và bảo trì.

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

Theo tôi, đây là giải pháp tốt nhất. Khi tôi thấy mã nhanh, hầu hết, khả năng đọc không tốt hơn objc. Nhưng nó có thể, nếu các lập trình viên chú ý nhiều hơn đến người đọc mã của họ. Bản thân trong tương lai của họ, ví dụ :)
Vilém Kurz

4

Xin lỗi, câu trả lời của tôi là cụ thể về cách tôi sử dụng bài đăng này trong những gì tôi cần làm. Đối với những người vấp phải câu hỏi này, tìm kiếm một cách để tìm ra một trường hợp trong enum, đây là cách để làm điều đó (mới trong Swift 2):

Chỉnh sửa: camelCase chữ thường hiện là tiêu chuẩn cho giá trị enum của 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)
        }
    }

Đối với những người thắc mắc về việc liệt kê trên một enum, các câu trả lời được đưa ra trên trang này bao gồm một var / let chứa tĩnh của tất cả các giá trị enum là chính xác. Mã ví dụ mới nhất của Apple cho tvOS chứa chính xác kỹ thuật này.

Điều đó đang được nói, họ nên xây dựng một cơ chế thuận tiện hơn vào ngôn ngữ (Apple, bạn có nghe không?)!


3

Thí nghiệm là: TRẢI NGHIỆM

Thêm một phương thức vào Thẻ tạo ra một cỗ bài đầy đủ, với một thẻ của mỗi kết hợp cấp bậc và bộ đồ.

Vì vậy, mà không sửa đổi hoặc tăng cường mã đã cho ngoài việc thêm phương thức (và không sử dụng công cụ chưa được dạy), tôi đã đưa ra giải pháp này:

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

Đây là một phương thức tôi sử dụng để lặp lại một enumvà cung cấp nhiều loại giá trị từ mộtenum

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

Đây là đầu ra:

Số Zero trong tiếng Pháp: zéro, tiếng Tây Ban Nha: cero, tiếng Nhật: nuru
Số một trong tiếng Pháp: un, tiếng Tây Ban Nha: uno, tiếng Nhật: ichi
Số hai trong tiếng Pháp: deux, tiếng Tây Ban Nha: dos, tiếng Nhật: ni
Số ba trong tiếng Pháp : trois, tiếng Tây Ban Nha: tres, tiếng Nhật: san
Số bốn trong tiếng Pháp: quatre, tiếng Tây Ban Nha: cuatro, tiếng Nhật: shi
Số năm bằng tiếng Pháp: cinq, tiếng Tây Ban Nha: cinco, tiếng Nhật: go
Số sáu trong tiếng Pháp: sáu, tiếng Tây Ban Nha: seis, tiếng Nhật: roku
Số bảy trong tiếng Pháp: sept, tiếng Tây Ban Nha: siete, tiếng Nhật: shichi

Tổng cộng có 8 trường hợp

siete trong tiếng Nhật: shichi


CẬP NHẬT

Gần đây tôi đã tạo ra một giao thức để xử lý việc liệt kê. Giao thức yêu cầu enum với giá trị 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

Điều này có vẻ giống như một hack nhưng nếu bạn sử dụng các giá trị thô, bạn có thể làm một cái gì đó như thế này

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

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

2

Trong khi giải quyết Swift 2.0ở đây là đề nghị của tôi:

Tôi đã thêm loại thô vào Suit enum

enum Suit: Int {

sau đó:

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

Như với câu trả lời @Kametrixom tại đây tôi tin rằng trả về một mảng sẽ tốt hơn trả về AnySequence, vì bạn có thể có quyền truy cập vào tất cả các tính năng của Array như đếm, v.v.

Đây là viết lại:

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

Giải pháp khác:

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

Đây là một bài viết khá cũ, từ Swift 2.0. Hiện tại có một số giải pháp tốt hơn ở đây sử dụng các tính năng mới hơn của swift 3.0: Lặp lại thông qua Enum trong Swift 3.0

Và trong câu hỏi này, có một giải pháp sử dụng một tính năng mới của (chưa được phát hành khi tôi viết bản chỉnh sửa này) Swift 4.2: Làm thế nào để tôi có được số đếm của một enum Swift?


Có rất nhiều giải pháp tốt trong chủ đề này và những giải pháp khác tuy nhiên một số trong số chúng rất phức tạp. Tôi muốn đơn giản hóa càng nhiều càng tốt. Đây là một giải pháp có thể hoặc không thể hoạt động cho các nhu cầu khác nhau nhưng tôi nghĩ rằng nó hoạt động tốt trong hầu hết các trường hợp:

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

Lặp lại:

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

1
Cảm giác đó giống như rất nhiều công việc dành riêng cho từng enum bạn đã tạo - tôi không chắc chắn rằng trả lại [Number.One.rawValue, Number.Two.rawValue, ...] trong trường hợp này .
Scott Austin

Đây là một bài viết khá cũ, từ Swift 2.0. Hiện tại có một số giải pháp tốt hơn sử dụng các tính năng mới hơn của swift 3.0: stackoverflow.com/questions/41352594/. Và về câu hỏi này, có một giải pháp sử dụng một tính năng mới của (chưa được phát hành khi tôi viết bản chỉnh sửa này ) Swift 4.2: stackoverflow.com/questions/27094878/ Kiếm
Abbey Jackson

2

Enums có toRaw()fromRaw()phương pháp. Vì vậy, nếu giá trị thô của bạn là một Int, bạn có thể lặp từ đầu đến cuối 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()
    }
}

Một điều đáng chú ý là bạn cần kiểm tra các giá trị tùy chọn trước khi chạy simpleDescriptionphương thức, vì vậy chúng tôi đặt convertedSuitgiá trị của chúng tôi trước rồi đặt hằng số thànhconvertedSuit.simpleDescription()


2
Câu hỏi ban đầu là về kiểu String enum không Int
cynistersix 11/11/14

2

Đây là cách tiếp cận được đề xuất của tôi. Nó không hoàn toàn thỏa đáng (tôi rất mới với Swift và OOP!) Nhưng có lẽ ai đó có thể tinh chỉnh nó. Ý tưởng là để mỗi enum cung cấp thông tin phạm vi .first.lastthuộc tính riêng của nó . Nó chỉ thêm hai dòng mã cho mỗi enum: vẫn hơi khó mã hóa, nhưng ít nhất nó không sao chép toàn bộ tập hợp. Nó đòi hỏi phải sửa đổi Suitenum thành một Int nhưRank enum, thay vì được tháo ra.

Thay vì lặp lại toàn bộ giải pháp, đây là mã tôi đã thêm vào .enum, ở đâu đó sau các câu lệnh tình huống ( Suitenum tương tự):

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

và vòng lặp tôi đã sử dụng để xây dựng bộ bài như một mảng của Chuỗi. (Định nghĩa vấn đề không nêu rõ cách thức cấu trúc bộ bài.)

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
}

Điều đó không thỏa đáng vì các thuộc tính được liên kết với một phần tử chứ không phải là enum. Nhưng nó làm tăng thêm sự rõ ràng cho các vòng lặp 'cho'. Tôi muốn nói Rank.firstthay vìRank.Ace.first . Nó hoạt động (với bất kỳ yếu tố nào), nhưng nó xấu. Ai đó có thể chỉ ra làm thế nào để nâng nó lên mức enum?

Và để làm cho nó hoạt động, tôi nhấc createDeckphương thức ra khỏi Thẻ struct. Tôi không thể tìm ra làm thế nào để có được một mảng [Chuỗi] được trả về từ cấu trúc đó và dường như đó là một nơi không tốt để đặt một phương thức như vậy.


2

Tôi đã làm điều đó bằng cách sử dụng thuộc tính được tính toán, trả về mảng của tất cả các giá trị (nhờ bài đăng này http://natecook.com/blog/2014/10/loopy-random-enum-ideas/ ). Tuy nhiên, nó cũng sử dụng giá trị int thô, nhưng tôi không cần lặp lại tất cả các thành viên liệt kê trong tài sản riêng.

CẬP NHẬT Xcode 6.1 đã thay đổi một chút cách sử dụng enum thành viên bằng cách sử dụng rawValue, vì vậy tôi đã sửa danh sách. Cũng sửa lỗi nhỏ với sai trước 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
    }
}

Dựa trên câu trả lời của Rick: việc này nhanh hơn 5 lần


Thêm một Counttrường hợp sẽ phá vỡ các switchtriển khai toàn diện và CaseIterablephù hợp.
Cœur
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.