Menu
Coddy logo textTech

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

CoddyのPHPジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 62/91。

ジェネレータは、完全な Iterator インターフェースを実装することなく、イテレータを作成するための簡単な方法を提供します。ジェネレータ関数は、値を1つずつ生成するために return の代わりに yield を使用し、各値の間で実行を一時停止します。

以下は、一連の数値を生成する基本的なジェネレータです:

<?php
function countTo(int $max): Generator {
    for ($i = 1; $i <= $max; $i++) {
        yield $i;
    }
}

foreach (countTo(3) as $number) {
    echo "$number\n";
}

出力:

1
2
3

主な利点はメモリ効率です。メモリ内に配列全体を構築する代わりに、ジェネレータはオンデマンドで値を生成します。これは大規模なデータセットを扱う際に非常に重要です。100万件のレコードを処理するジェネレータも、10件を処理するものと同じメモリしか使用しません。

ジェネレータは、キーと値のペアを yield することもできます:

<?php
function userGenerator(): Generator {
    yield 'admin' => 'Alice';
    yield 'editor' => 'Bob';
}

foreach (userGenerator() as $role => $name) {
    echo "$role: $name\n";
}

出力:

admin: Alice
editor: Bob

独自のクラスを foreach で反復可能にするには、Iterator インターフェースを実装します。しかし、ほとんどの場合、ジェネレータを使用すると、ボイラープレートコード(定型文)が少なく、よりクリーンなソリューションが得られます。これらは、OOP において、クラスがすべての項目を一度にメモリに読み込むことなくコレクションを公開する必要がある場合に特に有用です。

challenge icon

チャレンジ

簡単

すべてを一度にメモリに読み込むことなく、ジェネレータを使用して大規模なコレクションを効率的に反復処理する製品在庫システムを構築しましょう。製品を保存し、さまざまな方法でそれらを反復処理するためのジェネレータメソッドを提供するクラスを作成します。

コードは3つのファイルに分けて構成します。

  • Product.php — 在庫のアイテムを表すProductクラスを作成します。コンストラクタのプロモーションを使用して、publicな$sku(文字列)、publicな$name(文字列)、およびpublicな$price(浮動小数点数)を定義します。
  • Inventory.php — 製品のコレクションを管理し、それらを反復処理するためのジェネレータメソッドを提供するInventoryクラスを作成します。Productファイルをインクルードします。このクラスには以下の要件があります。
    • 製品を保存するためのprivateな配列プロパティを持つこと
    • コレクションに製品を追加するaddProduct(Product $product)メソッドを持つこと
    • 各製品を1つずつyieldするジェネレータメソッドallProducts(): Generatorを持つこと
    • 指定された最小値以上の価格を持つ製品のみをyieldするジェネレータメソッドexpensiveProducts(float $minPrice): Generatorを持つこと。このとき、キーをSKU、値を製品とするキーと値のペアをyieldします。
  • main.php — Inventoryファイルをインクルードします。入力として、最小価格のしきい値を1つ受け取ります。

    Inventoryを作成し、以下の3つの製品を追加します。

    • SKU: "A001"、Name: "Laptop"、Price: 999.99
    • SKU: "A002"、Name: "Mouse"、Price: 29.99
    • SKU: "A003"、Name: "Monitor"、Price: 349.99

    まず、allProducts()を反復処理し、各製品の名前をそれぞれの行に出力します。

    次に空行を出力し、続いて入力されたしきい値を使用してexpensiveProducts()を反復処理します。高価な各製品について、"[sku]: [name] - $[price]"をそれぞれの行に出力します。価格は小数点以下2桁にフォーマットしてください。

この課題は、ジェネレータを使用してコレクションを遅延評価で反復処理する方法を示しています。各製品は要求されたときにのみyieldされるため、このアプローチは大規模な在庫に対してメモリ効率が高くなります。expensiveProducts()でのキーと値のyieldは、ジェネレータが構造化された反復データを提供できることを示しています。

チートシート

ジェネレータは、returnの代わりにyieldを使用して、メモリ効率の良い方法でイテレータを作成する手段を提供します。これらは値を1つずつ生成し、各値の間で実行を一時停止します。

基本的なジェネレータ関数:

<?php
function countTo(int $max): Generator {
    for ($i = 1; $i <= $max; $i++) {
        yield $i;
    }
}

foreach (countTo(3) as $number) {
    echo "$number\n";
}

ジェネレータはキーと値のペアを生成(yield)することもできます:

<?php
function userGenerator(): Generator {
    yield 'admin' => 'Alice';
    yield 'editor' => 'Bob';
}

foreach (userGenerator() as $role => $name) {
    echo "$role: $name\n";
}

主な利点はメモリ効率です。ジェネレータは、メモリ内に配列全体を構築するのではなく、必要に応じて値を生成します。これは、OOP(オブジェクト指向プログラミング)のコンテキストで大規模なデータセットやコレクションを扱う場合に特に便利です。

自分で試してみよう

<?php

require_once 'Inventory.php';

// 入力を読み込む
$minPrice = floatval(trim(fgets(STDIN)));

// TODO: Inventory インスタンスを作成する

// TODO: 3つの製品を追加する:
// SKU: "A001", 名前: "Laptop", 価格: 999.99
// SKU: "A002", 名前: "Mouse", 価格: 29.99
// SKU: "A003", 名前: "Monitor", 価格: 349.99

// TODO: allProducts() をループして、各製品の名前を表示する

// TODO: 空行を表示する

// TODO: $minPrice を使用して expensiveProducts() をループする
// 各高額製品について、"[sku]: [name] - $[price]" を表示する
// 価格は小数点以下2桁でフォーマットする

?>
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン