0
1

More than 1 year has passed since last update.

【Swift】公式ドキュメントのIteratorProtocolを読む

Posted at

この記事は何?

SwiftのIteratorProtocolについて調べました。
主に、開発者ドキュメント
より翻訳した内容になります。

Declaration

宣言

protocol IteratorProtocol<Element>

Overview

概要

Swiftにおいて、「連続して、値を一つずつ取り出せるデータ構造」をシーケンスといいます。
Sequenceプロトコルに適合する型のインスタンスはfor-inループを使って、要素ごとに処理を反復できます。
その際、シーケンスに対して「一つずつ値を取り出す役割」を果たすインスタンスがイテレータです。
インスタンスの型がIteratorProtocolプロトコルに適合する場合、makeIterator()メソッドでイテレータを取得できます。

IteratorProtocolプロトコルはSequenceプロトコルと密接に関わっています。
イテレータは反復処理を追跡して、シーケンスを進めながら要素を一つずつ返します。

配列、セット、または他のコレクションやシーケンスでfor-inループを使用すると、その型のイテレータを使用しています。
Swiftはfor-inループを実行する際、シーケンスまたはコレクションのイテレータを内部的に使用します。

シーケンスのイテレータを直接使用すると、for-inループを使用して「そのシーケンス上で反復処理する」のと同じ順序で要素にアクセスできます。
以下の例では、for-inループを利用して配列の各要素を表示します。

let animals = ["Antelope", "Butterfly", "Camel", "Dolphin"]
for animal in animals {
    print(animal)
}
// Prints "Antelope"
// Prints "Butterfly"
// Prints "Camel"
// Prints "Dolphin"

内部的には、Swiftはanimals配列のイテレータを使用して配列の構成要素をループしています。

var animalIterator = animals.makeIterator()
while let animal = animalIterator.next() {
    print(animal)
}
// Prints "Antelope"
// Prints "Butterfly"
// Prints "Camel"
// Prints "Dolphin"

animals.makeIterator()メソッドは「配列のイテレータのインスタンス」を返します。
そして、whileループはイテレータのnext()メソッドを繰り返し呼び出し、取得した各要素をanimalにバインドし、next()メソッドがnilを返すと終了します。

Using Iterators Directly

イテレータを使用するには

for-inループはSwiftでシーケンスのデータを横断するためのより慣用的なアプローチです。
したがって、わざわざ、イテレータを直接使用する必要はないでしょう。
ただし、一部のアルゴリズムでは直接、イテレータを使用する必要があるかもしれません。

1つの例として、reduce1(_:)メソッドを挙げます。
reduce1(_:)メソッドは標準ライブラリのreduce(_:_:)メソッドと同様に、初期値と結合クロージャを受け取ります。
そして、「シーケンスの最初の要素」を初期値として使用します。

以下は、reduce1(_:)メソッドの実装です。
シーケンスの残りの部分をループする前に「初期値を取得する」ために、シーケンスのイテレータを直接使用しています。

extension Sequence {
    func reduce1(_ nextPartialResult: (Element, Element) -> Element) -> Element?
    {
        var i = makeIterator()
        guard var accumulated = i.next() else {
            return nil
        }

        while let element = i.next() {
            accumulated = nextPartialResult(accumulated, element)
        }
        return accumulated
    }
}

Reduce1(_:)メソッドは、特定の種類のシーケンス操作をより簡単にします。
先ほど紹介したanimals配列を例として、シーケンス内で最も長い文字列を見つける方法は次のとおりです。

let longestAnimal = animals.reduce1 { current, element in
    if current.count > element.count {
        return current
    } else {
        return element
    }
}
print(longestAnimal)
// Prints Optional("Butterfly")

Using Multiple Iterators

複数のイテレータを使用するには

単一のシーケンスで複数のイテレータ(またはfor-inループ)を使用するときは、その具体的な型を知っているか、シーケンスがcollectionプロトコルにも制限されているため、特定のシーケンスが繰り返し反復をサポートすることを知っていることを確認してください。
Whenever you use multiple iterators (or for-in loops) over a single sequence, be sure you know that the specific sequence supports repeated iteration, either because you know its concrete type or because the sequence is also constrained to the Collection protocol.

コピーするのではなく、シーケンスのmakeIterator()メソッドをその都度呼び出して、別個のイテレータを取得します。
イテレータをコピーすることは安全ですが、同じイテレータのnext()メソッドを呼び出してコピーを進めると、そのイテレータの他のコピーが無効になる恐れがあります。
この点でfor-inループは安全です。

Adding IteratorProtocol Conformance to Your Type

独自の型をIteratroProtocolに適合させるには

IteratorProtocolに適合するためのイテレータは、簡単に実装できます。
関連するシーケンスのステップを1つ進めて、現在の要素を返すnext()メソッドを宣言します。
シーケンスを最後まで進めたら、next()メソッドはnilを返します。

例として、独自のCountdown型シーケンスを考えます。
カウントダウンのシーケンスを開始する整数で初期化したら、ゼロまで反復処理できます。
Countdown構造体の定義はわずかです。
Sequenceプロトコルに必要な開始カウントとmakeIterator()メソッドのみが含まれています。

struct Countdown: Sequence {
    let start: Int

    func makeIterator() -> CountdownIterator {
        return CountdownIterator(self)
    }
}

makeIterator()メソッドは、独自に定義したCountdownIterator型のイテレータを返します。
CountdownIterator型は、「反復中のカウントダウンシーケンス」と「値を返した回数」の両方を追跡します。

struct CountdownIterator: IteratorProtocol {
    let countdown: Countdown    // 反復中のシーケンス
    var times = 0               // 値を返した回数

    // 初期化時に「反復中のシーケンス」を受け取る
    init(_ countdown: Countdown) {
        self.countdown = countdown
    }

    // シーケンスを進める
    mutating func next() -> Int? {
        let nextNumber = countdown.start - times  // 開始が10で3回目なら、次は7
        // 次の数が0より大きくなければ、nilを返して終了
        guard nextNumber > 0 else { return nil }

        // 次の数が0より大きいなら「値を返した回数」をインクリメントして、「次の数」を返す
        times += 1
        return nextNumber
    }
}

CountdownIteratorインスタンスでnext()メソッドが呼び出されるたびに、新しい次の値を計算し、ゼロに達したかどうかを確認し、イテレータが終了した場合はnilを、そうでなければ「シーケンスの要素」を返します。

カウントダウンのシーケンスを作成して反復処理するには、CountdownIterator型インスタンスに対して反復処理を行います。

let threeTwoOne = Countdown(start: 3)
for count in threeTwoOne {
    print("\(count)...")
}
// Prints "3..."
// Prints "2..."
// Prints "1..."
0
1
0

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
0
1