LoginSignup
56
49

More than 5 years have passed since last update.

[Swift] シンプルなカウントアップでSequenceに強くなる

Last updated at Posted at 2017-03-19

少年は言った。

「0から100までカウントアップしたい!!」

よし、じゃあ今日は0から100までの整数値を出力する、Sequenceの仲間たちを紹介するよ!
一緒にSequenceに強くなろう!:muscle:

Sequenceのチカラ

まずはSequenceとは何かということと、そのパワーについて知りましょう。

Sequenceとは

Sequenceとは、標準ライブラリのの中にある以下のようなprotocolです。

public protocol Sequence {
    public func makeIterator() -> Self.Iterator
}

makeIteratorを実装することによりSequenceに準拠することができます。Self.IteratorはIteratorProtocolを実装するクラスもしくは構造体で、makeIteratorは単にIteratorProtocolに準拠するインスタンスを返す関数です。IteratorProtocolとは以下のようなprotocolです。

public protocol IteratorProtocol {
    public mutating func next() -> Self.Element?
}

Self.Elementはイテレートする任意の要素型です。IteratorProtocolはnext()を呼ぶたびに「次の要素」を返し、最後の要素の次にnilを返すように実装します。

Sequenceに準拠するといいこと

Sequenceに準拠することにより、containsforEach, map, filter, reduceなどなど、多数の便利な関数を利用できるようになります。うまく使えば強力な独自クラスをつくることも可能です。

Sequenceとfor-in

さらに、SwiftではSequenceプロトコルに準拠することにより、for-inでイテレートできるようになります。標準ライブラリでよく使うArrayやDictionaryなどもSequenceプロトコルに準拠しているため、for-inでイテレートできるようになっています。以下の例はArrayをfor-inでイテレートしています。

let numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for i in numbers {
    print("\(i)") // 0, 1, 2...
}

さて、ここからは0から100までを出力する様々なSequenceを作る方法を紹介していきます。

カウントアップするSequenceを作る様々な方法

Closed Range Operatorを使う

おそらく最も一般的な方法はRange Operatorを使う方法です。

for i in 0...100 {
    print("\(i)") // 0, 1, 2...
}

なにも問題ありません。シンプルで過不足無く美しいコードです。

0...100CountableClosedRangeインスタンスを生成します。これはstructであり、Sequenceに準拠しているためfor-inでイテレートできます。

SequenceとCollectionの違い

実際のところ、CountableClosedRangeは単なるSequenceではなく、さらに高機能なRandomAccessCollectionに準拠しています。SequenceとCollectionはfor-inの挙動において抑えておきたい違いが2点あります

  1. Sequenceは一度イテレートすると二回め以降は同じようにイテレートできることが保証されていませんが、Collectionは何度もイテレート可能です
  2. SequenceはIteratorの実装によっては無限に続く可能性もありますが、Collectionは添字アクセスを可能にするため、有限であることが保証されています

詳しくは公式リファレンスの "Traversing a Collection" を参照してください。
https://developer.apple.com/reference/swift/collection

Sequenceを自分で実装する

SequenceとIteratorProtocolを実装する

自分でSequenceを実装する方法をご紹介します。

struct CountUp: Sequence, IteratorProtocol {
    private var count = 0
    mutating func next() -> Int? {
        defer { count += 1 }
        return count <= 100 ? count : nil
    }
}

for i in CountUp() {
  print("\(i)") // 0, 1, 2...
}

IteratorProtocolとSequenceを別々に実装することもできますが、こちらのほうが同時に実装できてシンプルですね。しかもこの場合makeIterator()はデフォルト実装を利用できるため、自分で実装する必要はありません。

公式でもこの書き方が例として紹介されています。
https://developer.apple.com/reference/swift/sequence

AnySequenceを使う

AnySequenceを使うと、具体的なクラスを作ることなく独自処理のSequenceを実装することができます。

let countUp = AnySequence { () -> AnyIterator<Int> in
    var count = 0
    return AnyIterator {
        defer { count += 1 }
        return count <= 100 ? count : nil
    }
}

for i in countUp {
    print("\(i)") // 0, 1, 2...
}

AnySequence自体はSequenceの具体的な型を隠蔽して扱う(Type Erasure)ためのクラスですが、今回のように一時的な無名Sequenceを作る用途でも利用できます。初期化時にmakeIteratorの実装をクロージャで渡すようなイメージです。

StrideThroughを使う

StrideThroughというSequenceがあります。stride(from:through:by:)関数を用いて、指定した閉区間をストライドで区切ったSequenceを作ることができます。

let countUp = stride(from: 0, through: 100, by: 1)
for i in countUp {
    print("\(i)") // 0, 1, 2...
}

byに2を渡せば 0, 2, 4, 6... と1つ飛ばしにもできますし、stride(from: 100, through: 0, by: -1)のようにマイナスを渡せば 100, 99, 98... と減っていくようにもできます。

また、半開区間をストライドで区切るStrideToと言うものあります。そちらはstride(from:to:by:)を使います。StrideThroughと違い、toで指定した値は出力されません。

UnfoldSequenceを使う

UnfoldSequenceというSequenceがあります。カウントアップは以下のように書けます。

let countUp = sequence(first: 0) { prev -> Int? in
    return prev >= 100 ? nil : prev + 1
}

for i in countUp {
    print("\(i)") // 0, 1, 2...
}

prevは1つ前に出力した値です。UnfoldSequenceは1つ前の値を保持しており、1つ前の値から次の値を計算して出力しています。今回は単に1つ前の値に1を足して次の出力としています。

ただし、UnfoldSequence自体は単に1つ前の値だけを保持するだけのものではありません。より正確に言えば、UnfoldSequenceはイテレーションを通して状態を持ち、出力する要素を毎回計算するSequenceです。イテレーションを通して状態を持ち続けたい場合はsequence(state:next:)という関数を使います。stateには自由な値を設定でき、イテレーションを通してアクセスすることができます。

UnfoldSequenceは使いどころが難しいですが、無限に続く数列を定義したり、木構造の親を辿っていくような再帰的な処理に使ったり、2つのイテレータを交互に出力したりなど、工夫次第で様々な用途で使えるようです。

まとめ

さまざまな方法でカウントアップしてみましたが、いかがでしたでしょうか。
Sequenceに強くなれたかな?:angel:


:whale2: Twitter: @a_beco
:guitar: SoundCloud: @beco <- 作業用BGMにどうぞ!

56
49
2

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
56
49