第15章 イテレータとジェネレータ

イテレータとは?

イテレータは、コレクション(配列やリスト、マップなど)を順番に走査するためのメカニズムを提供するオブジェクトです。JavaScriptのイテレータは、

next()

メソッドを提供し、このメソッドは

{value, done}

という形式のオブジェクトを返します。

  • value: 現在の要素の値
  • done: イテレータが終了したかどうか(終了していればtrue、まだ要素が残っていればfalse)


イテレータの作成

イテレータは、オブジェクトに[Symbol.iterator]メソッドを実装することで作成されます。このメソッドは、nextメソッドを持つオブジェクトを返すべきです。

const range = {
    start: 1,
    end: 5,
    [Symbol.iterator]: function() {
        let currentValue = this.start;
        const self = this;
        return {
            next: function() {
                if (currentValue <= self.end) {
                    return { value: currentValue++, done: false };
                } else {
                    return { value: undefined, done: true };
                }
            }
        };
    }
};

for (let n of range) {
    console.log(n);  // 1, 2, 3, 4, 5 を順に表示
}


イテレータの使用

イテレータは直接nextメソッドを呼び出すことで、要素を一つずつ取得することができます。また、for...ofループを使用して、簡単に走査することができます。

const array = [1, 2, 3, 4, 5];
const iterator = array[Symbol.iterator]();

console.log(iterator.next());  // {value: 1, done: false}
console.log(iterator.next());  // {value: 2, done: false}
// ... 続けることができる

for (let value of array) {
    console.log(value);  // 1, 2, 3, 4, 5 を順に表示
}


イテレータの使用場面

  • カスタムのデータ構造: カスタムデータ構造(例えば、特定の種類の木やグラフ)に対して、要素を一つずつ取り出すメカニズムを提供したい場合、イテレータを実装することで、そのデータ構造をfor...ofループなどの標準的なJavaScriptの機能で扱えるようになります。
  • 遅延評価: イテレータは次の要素を返すnext()メソッドを持っているので、それぞれの要素は必要になったときにのみ計算されます。この性質を利用して、大きなデータセットや無限の数列を効率的に扱うことができます。
  • 外部データソースの読み取り: 外部データソース(例えば、ファイルやデータベース)からのデータの読み取りにイテレータを用いることで、そのデータを一度にすべてメモリに読み込むことなく、必要に応じて少しずつ取り扱うことができます。
  • 制御された反復処理: イテレータは反復処理の進行を細かくコントロールすることができるので、特定の条件で反復を中断したり、特定の要素をスキップするなどのカスタムな反復ロジックを実装したい場合に有効です。
  • APIの一貫性: 組み込みの配列や文字列、マップなどのJavaScriptのオブジェクトと同じように、カスタムオブジェクトもfor...ofループやスプレッド構文で扱えるようにすることで、APIの一貫性を保つことができます。
  • データの変換やフィルタリング: イテレータを連鎖させることで、データの変換やフィルタリングを行うパイプラインを構築することができます。


イテレータまとめ

イテレータは、要素のコレクションを繰り返し処理する統一的なインターフェースを提供します。JavaScriptでは、配列や文字列、Map、Setなどの組み込みオブジェクトにはデフォルトでイテレータが実装されているため、for...ofループで直接使うことができます。


ジェネレータとは?

ジェネレータは、JavaScriptにおける特別なタイプの関数です。これは、実行を一時停止して後から再開することができる関数です。ジェネレータ関数は、イテレータと同じような{value, done}形式のオブジェクトを返すnext()メソッドを持つオブジェクトを返します。


ジェネレータ関数の定義

ジェネレータ関数は、function*の構文を使用して定義されます。

function* numberGenerator() {
    yield 1;
    yield 2;
    yield 3;
}

この関数は、3つの値(1, 2, 3)を順番に生成します。


ジェネレータの使用

ジェネレータ関数を呼び出すと、ジェネレータオブジェクトが返されます。このオブジェクトのnext()メソッドを呼び出すことで、yieldで指定された値を一つずつ取得できます。

const gen = numberGenerator();

console.log(gen.next());  // {value: 1, done: false}
console.log(gen.next());  // {value: 2, done: false}
console.log(gen.next());  // {value: 3, done: false}
console.log(gen.next());  // {value: undefined, done: true}


yieldの使用

yieldはジェネレータ関数の中でのみ使用できます。ジェネレータ関数の実行は、yieldに到達すると一時停止し、yieldの右側の式の値がnext()の戻り値として返されます。次にnext()が呼び出されると、ジェネレータは停止していた場所から実行を再開します。


ジェネレータとイテレータ

ジェネレータオブジェクトはイテレータプロトコルに従っているため、for...ofループで直接使うことができます。

for (let value of numberGenerator()) {
    console.log(value);  // 1, 2, 3 を順に表示
}


ジェネレータの使用場面

  • 遅延評価: ジェネレータは要素を遅延評価します。つまり、要素は実際に必要になった時点でのみ生成されます。これにより、非常に大きなデータセットや無限の数列を効率的に扱うことができます。
  • 外部データソースの読み取り: データベースのクエリ結果やファイルの読み取りなど、外部データソースからのデータを少しずつ処理する場合、ジェネレータを使用するとメモリ使用量を節約しながらデータを順次処理することができます。
  • 非同期タスクの管理: ジェネレータとPromiseを組み合わせることで、非同期タスクを直感的にかつシーケンシャルに実行するコードを書くことができます。特定のライブラリやフレームワークでは、この組み合わせを用いた非同期処理の方法をサポートしていることがあります。
  • 状態の保持: ジェネレータは内部状態を保持しつつ、それを外部から操作することができます。これにより、カスタムのイテレータや状態機械を簡単に実装することができます。
  • データの変換やフィルタリング: ジェネレータを連鎖させることで、データの変換やフィルタリングを行うパイプラインを構築することができます。この点ではイテレータと類似していますが、ジェネレータの方が簡潔に実装できることが多いです。
  • コルーチン: ジェネレータはコルーチンとして使用することができます。これにより、複数の関数やタスクが協力して実行を進めることができ、複雑なタスクの分割や並列処理を行うことが容易になります。

ジェネレータまとめ

ジェネレータは、複雑な繰り返し処理や非同期タスクの処理、無限のデータストリームの生成など、さまざまな用途で使用されます。function*の構文とyieldキーワードを使用することで、独自のジェネレータ関数を定義することができます。


練習問題1.

整数の配列を受け取り、次の値へ移動するnext()メソッドを持つイテレータを返す関数createNumberIterator(numbers)を作成してください。

使用例

const iterator = createNumberIterator([1, 2, 3]);
console.log(iterator.next());  // {value: 1, done: false}
console.log(iterator.next());  // {value: 2, done: false}
console.log(iterator.next());  // {value: 3, done: false}
console.log(iterator.next());  // {value: undefined, done: true}


練習問題2.

0から指定された数までのフィボナッチ数列を生成するジェネレータ関数fibonacciGenerator(n)を作成してください。

使用例

const fib = fibonacciGenerator(4);
console.log(fib.next());  // {value: 0, done: false}
console.log(fib.next());  // {value: 1, done: false}
console.log(fib.next());  // {value: 1, done: false}
console.log(fib.next());  // {value: 2, done: false}
console.log(fib.next());  // {value: undefined, done: true}


練習問題3.

整数の配列を受け取り、その配列内の各要素を2倍にした新しい数列を生成するジェネレータ関数doubleNumbersGenerator(numbers)を作成してください。このジェネレータ関数は、指定されたイテレータを使用して元の数列の要素にアクセスする必要があります。

使用例

const originalIterator = createNumberIterator([1, 2, 3]);  // 前の練習問題で作成した関数を使用
const doubled = doubleNumbersGenerator(originalIterator);
console.log(doubled.next());  // {value: 2, done: false}
console.log(doubled.next());  // {value: 4, done: false}
console.log(doubled.next());  // {value: 6, done: false}
console.log(doubled.next());  // {value: undefined, done: true}