Menu
Coddy logo textTech

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

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

ジェネレータは、完全な Iterator インターフェースを実装することなく、イテレータを作成するための簡単な方法を提供します。ジェネレータ関数は、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";
}

出力:

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 クラスを作成します。コンストラクタのプロパティ昇格(constructor promotion)を使用して、public な $sku (string)、public な $name (string)、および public な $price (float) を定義してください。
  • 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() を反復処理し、各製品の名前をそれぞれの行に出力します。

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

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

自分で試してみよう

<?php

require_once 'Inventory.php';

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

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

// TODO: 3つの商品を追加する:
// SKU: "A001", Name: "Laptop", Price: 999.99
// SKU: "A002", Name: "Mouse", Price: 29.99
// SKU: "A003", Name: "Monitor", Price: 349.99

// TODO: allProducts() をループして、各商品の名前を出力する

// TODO: 空行を出力する

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

?>
quiz icon腕試し

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

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