35

Is it possible to loop through enum values in Swift? Or what is the alternative?

I'm working through Apple's Swift language guide, and I came across this example on enums.

//  EXPERIMENT
//
//  Add a method to Card that creates a full deck of cards, 
//  with one card of each combination of rank and suit.

struct Card {
    var rank: Rank
    var suit: Suit
    func simpleDescription() -> String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }
}
let threeOfSpades = Card(rank: .Three, suit: .Spades)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()

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

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.toRaw())
        }
    }
}

Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/us/jEUH0.l

I've tried the following, but the docs say enums in Swift are not assigned underlying integer values like in C, so I'm probably barking up the wrong tree.

Is there a better way solve this problem?

func deck() -> Card[]{
    var deck: Card[]
    for s in Suit {
        for r in Rank {
            deck += Card(rank: r, suit: s)
        }
    }
    return deck
}

func deck2() -> Card[]{
    var deck: Card[]
    for var s: Suit = .Spades; s <= .Clubs; s++ {
        for var r: Rank = .Ace; r <= .King; r++ {
            deck += Card(rank: r, suit: s)
        }
    }
    return deck
}
Pramod More
  • 1,220
  • 2
  • 22
  • 51
glaslong
  • 455
  • 1
  • 4
  • 7
  • @MartinR Thanks, I didn't find that in my initial search for some reason. Slight difference though, I'm also looking for ways to accomplish the goal of building the deck of cards without looping, if possible. Probably should have made that more clear in the post. – glaslong Jun 05 '14 at 14:09
  • 1
    Well, it seems Swift doesn't have an especially easy way to iterate over enum members without defining them as integers. A bit disappointing, but I suppose I shouldn't be using enums like this anyway. Still curious why they used it this way in the book when a dictionary seems like a much better choice for this scenario. – glaslong Jun 09 '14 at 13:13

1 Answers1

23

Is there another way? Sure. Is it better, that's for you to decide:

func generateDeck() -> Card[]
{
    let ranksPerSuit = 13
    var deck = Card[]()

    for index in 0..52
    {
        let suit = Suit.fromRaw(index / ranksPerSuit)
        let rank = Rank.fromRaw(index % ranksPerSuit + 1)

        let card = Card(rank: rank!, suit: suit!)
        deck.append(card)
    }

    return deck
}

let deck = generateDeck()

for card : Card in deck { println("\(card.description)") }

To use this, you will need to make sure that Rank and Suit enums both use Int for their type definitions (ex: enum Rank : Int).

Rank.Ace should equal 1 and the first Suit case should equal 0.

If you want to loop similar to your existing code, you should still make your enums Int types so you can use Rank.King.toRaw() and the like.

The Apple documentation states that enums are not restricted to being 'simply integer values', but certainly can be if you desire them to be.

UPDATE

Idea taken from comment by @jay-imerman, and applicable to Swift 5

extension Rank: CaseIterable {}
extension Suit: CaseIterable {}

func generateDeck() -> [Card] {
    var deck = [Card]();

    Rank.allCases.forEach {
        let rank = $0

        Suit.allCases.forEach {
            let suit = $0

            deck.append(Card(rank: rank, suit: suit))
        }
    }

    return deck;
}
dentuzhik
  • 742
  • 8
  • 18
Erik
  • 12,730
  • 5
  • 36
  • 42
  • This is one of the better answers I've seen. It's clean, short, and doesn't try to ditch the enums for something else. Thank you! – glaslong Jun 09 '14 at 13:08
  • 1
    I don't know, I'm not convinced. My problem with this is that it hard-codes the iteration limits, instead of inferring them from the enumerations. I want to know, in code how to obtain the limits/bounds of the enumeration and use that in a for loop... – Jay Imerman Jun 17 '14 at 01:48
  • 4
    I found a VERY elegant answer posted by sdduursma on this question on the same http://stackoverflow.com/questions/24007461/how-to-enumerate-an-enum-with-string-type – Jay Imerman Jun 17 '14 at 02:09
  • stackoverflow code coloring not working for variables in string expression at last line :) – Amr Lotfy Mar 08 '16 at 19:18
  • You have to compensate for the 0 in the index so set -> for index in 0...51 – uplearned.com Apr 08 '16 at 22:36
  • For people wondering, iteratation will go from `top` to `bottom`. I mean it sounds obvious but for the sake of being percise~ – itMaxence Apr 30 '20 at 16:46