2014年6月6日金曜日

Swift の enum型を for-in でイテレーションする方法

例えばJavaのEnum型などはそのまま以下のようにイテレーションすることが可能なのですが、
public enum Suit {
Spades, Hearts, Diamonds, Clubs;
}
for (Suit suit : Suit.values()) {
System.out.println(suit.toString());
}

なぜかSwiftのenum型はそのままではイテレーションすることができません。対策としてGeneratorという仕組みが標準ライブラリに用意されてますので、それを使ってenumをイテレーションできるようにします。

具体的には、Generatorを継承したクラスを作成して next() -> Element? を実装してください。ElementはAnyObjectのtypealiasなので実際には好きな型を返していただければOKです。あとはSequenceOf<T>型でGeneratorをラップしてあげればOKです。next()メソッドがnilを返すまでSequenceOf<T>はイテレーションを続けてくれます。

以下にサンプルコードを示します。
enum Rank:Int {
case Ace = 1
case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten
case Jack, Queen, King
// Here's an undocumented trick: How to enumerate enums using Generator!
// I wish I could use yield though ;(
class EnumGenerator:Generator {
var i = 1
func next() -> Rank? {
let e = Rank.fromRaw(i)
i++
return e
}
}
static func enumerate() -> SequenceOf<Rank> {
return SequenceOf<Rank>({EnumGenerator()})
}
}
enum Suit:String {
case Spades = "Spades"
case Hearts = "Hearts"
case Diamonds = "Diamonds"
case Clubs = "Clubs"
class EnumGenerator:Generator {
var current:Suit? = Suit.Spades
func next() -> Suit? {
let e = self.current
if let c = self.current {
switch c {
case .Spades:
self.current = Suit.Hearts
case .Hearts:
self.current = Suit.Diamonds
case .Diamonds:
self.current = Suit.Clubs
default:
self.current = nil
}
}
return e
}
}
static func enumerate() -> SequenceOf<Suit> {
return SequenceOf<Suit>({EnumGenerator()})
}
}
struct Card {
var rank:Rank
var suit:Suit
static func fullSetOfCards()->Array<Card> {
var result = Array<Card>()
for suit in Suit.enumerate() {
for rank in Rank.enumerate() {
let card = Card(rank: rank, suit: suit)
result.append(card)
}
}
return result
}
}
let deck = Card.fullSetOfCards()

Generator内部でyieldが使えれば便利なんですが、おそらくyield構文は無さそうです。Enum型を一覧したい場合以外にもGeneratorは便利に使えますのでぜひお試しください。