LoginSignup
5
2

More than 5 years have passed since last update.

Swiftでフィボナッチ数列ジェネレーターをサクッと作る

Last updated at Posted at 2018-01-13

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 さんありがとうございました。

参考

以上になります。
お読み頂きありがとうございました。

5
2
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2