Swift初心者レベルですが、勉強会でイテレーターについて色々と学んだので「ジェネレーターもサクッと作れそうだな」と思い作ってみました。
メモ程度ですが、参考になれば幸いです。
その勉強会→みんなで Swift 復習会 GO! in 京都 – 6th′
概要
- ジェネレーターの例にありがちなフィボナッチ数を作ってみる
- クロージャを使う
-
UnfoldSequenceを使う
- GeneratorTypeというprotocolがあるみたいですが、UnfoldSequence使った実装のほうが簡潔に感じました
- deferで関数を抜ける際にステートを更新
- Go言語にもあるリソースのクローズとかによく使われるやつ
実装
func generateFibonacci() -> UnfoldSequence<Int, (current: Int, next: Int)> {
// クロージャ関数を返す
return sequence(state: (0, 1)) { state -> Int in
// 関数を抜けるときにステートを更新
defer {
state = (
current: state.next,
next: state.current + state.next
)
}
return state.current
}
}
// fibonacci変数にはUnfoldSequnceのインスタンスが格納される
var fibonacci = generateFibonacci()
// UnfoldSequenceのnext()関数を実行すると現在の値を取得し、
// フィボナッチ数を更新
print(fibonacci.next()) // Optional(0)
print(fibonacci.next()) // Optional(1)
print(fibonacci.next()) // Optional(1)
print(fibonacci.next()) // Optional(2)
print(fibonacci.next()) // Optional(3)
print(fibonacci.next()) // Optional(5)
print(fibonacci.next()) // Optional(8)
print(fibonacci.next()) // Optional(13)
print(fibonacci.next()) // Optional(21)
print(fibonacci.next()) // Optional(34)
print(fibonacci.next()) // Optional(55)
print(fibonacci.next()) // Optional(89)
print(fibonacci.next()) // Optional(144)
UnfoldSequenceはSequenceプロトコルだけでなくIteratorProtocolにも準拠しているためnext()
が使えるようになっています。Sequenceだけだとnext()
は使えませんが、makeIterator()
でイテレーターインスタンスを取得することでnext()
を使うことができます。このことは以下に追記しているので併せて拝見していただけると幸いです。
個人的にはswiftにもyieldがあれば、ぱっと見たときにジェネレーター関数ってわかりやすいかもなと感じました。
あと、JavaScriptのジェネレーターみたいにnext().valueとしなくていいのは良いなと感じました。
追記(2018/01/16)
以下のような方法でも同じようなことができます。@es_kumagai さんにTwitter上で教えていただいたことを基にしています。ありがとうございます!
next()
は使えませんが、そのかわりにsequence
の実装がスッキリ書けました。
配列の添字用にindex()
関数を作りましたが、これはSwiftに++
のインクリメントが無いためcount++
の代用です。
makeIterator()
を使えば配列の添字を意識する必要はないことも教えていただきました。makeIterator()
でIteratorProtocolに準拠したオブジェクトを生成します。そのため、next()
関数を使用することができます。
ちなみにXcode9のplaygroundでは動きましたが、Xcode8では動きませんのでご注意ください。
@es_kumagaiさんとのTwitter上でのやりとり
let fibonacci = sequence(state: (current: 0, next: 1)) { state -> Int in
defer {
state = (current: state.next, next: state.current + state.next)
}
return state.current
}
var iterator = fibonacci.makeIterator()
print(iterator.next()) // Optional(0)
print(iterator.next()) // Optional(1)
print(iterator.next()) // Optional(1)
print(iterator.next()) // Optional(2)
print(iterator.next()) // Optional(3)
print(iterator.next()) // Optional(5)
print(iterator.next()) // Optional(8)
print(iterator.next()) // Optional(13)
print(iterator.next()) // Optional(21)
print(iterator.next()) // Optional(34)
print(iterator.next()) // Optional(55)
print(iterator.next()) // Optional(89)
print(iterator.next()) // Optional(144)
最終的にかなり簡潔になりました。
かなりサクッとフィボナッチ数を返すイテレーターオブジェクトができました。
@es_kumagai さんやコメントくださった @lovee さん、 @t-ae さんありがとうございました。
参考
- UnfoldSequence
- Swift 2のdefer文
- Swift 3 でジェネレータを作ったり、遅延評価してみる
- GeneratorとSequence
- [Swift] シンプルなカウントアップでSequenceに強くなる
- Go言語のdeferでファイルリソースをクローズする
以上になります。
お読み頂きありがとうございました。