ジェネレーターとイテレーター
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最大の利点はメモリ効率です。メモリ上に完全な array を構築する代わりに、generator は必要に応じて値を生成します。これは大規模なデータセットを扱う場合に非常に重要です。100万件のレコードを処理する generator も、10件を処理する generator と同じメモリしか使用しません。
ジェネレーターはキーと値のペアを生成することもできます:
<?php
function userGenerator(): Generator {
yield 'admin' => 'Alice';
yield 'editor' => 'Bob';
}
foreach (userGenerator() as $role => $name) {
echo "$role: $name\n";
}
出力:
admin: Alice
editor: Bobforeachで自分のclassを反復可能にするには、IteratorインターフェースをImplementします。ただし、ほとんどの場合、generatorを使うほうが、ボイラープレートコードが少なく、よりすっきりした解決策になります。特にOOPでは、すべてを一度にメモリへ読み込まずに、classが項目のcollectionを公開する必要がある場合に便利です。
チャレンジ
簡単すべてを一度にメモリへ読み込まず、大規模な collection を効率的に Iterate するために generator を使用する product inventory system を構築しましょう。product を格納し、さまざまな方法で Iterate するための generator method を提供する class を作成します。
コードを3つのファイルに分けて整理します。
Product.php: inventory 内の item を表すProductclass を Create します。constructor promotion を使用して、public$sku(string)、public$name(string)、public$price(float)を define します。Inventory.php: product の collection を管理し、それらを Iterate する generator method を提供するInventoryclass を Create します。Product file を Include します。class には次の内容を含めます。- product を格納する private array property を持つ
- product を collection に Add する
addProduct(Product $product)method を持つ - 各 product を one at a time で yield する
allProducts(): Generatorgenerator method を持つ - 指定された minimum 以上の price を持つ product only を yield し、key が SKU、value が product となる key-value pair を yield する
expensiveProducts(float $minPrice): Generatorgenerator method を持つ
main.php: Inventory file を Include します。input は1つ、つまり minimum price threshold を受け取ります。Inventoryを Create し、次の3つの product を Add します。- SKU:
"A001"、Name:"Laptop"、Price:999.99 - SKU:
"A002"、Name:"Mouse"、Price:29.99 - SKU:
"A003"、Name:"Monitor"、Price:349.99
まず、
allProducts()を Iterate し、各 product の name をそれぞれ別の行に print します。次に empty line を print し、その後、input threshold を使用して
expensiveProducts()を Iterate します。expensive product ごとに、"[sku]: [name] - $[price]"をそれぞれ別の行に print します。price は小数点以下 exactly 2桁で Format します。- SKU:
この challenge は、generator によって collection を lazy に Iterate できる仕組みを示しています。各 product は要求されたときにのみ yield されるため、大規模な inventory でこの approach は memory-efficient です。expensiveProducts() における key-value の yield は、generator が structured iteration data を提供できる方法を示しています。
自分で試してみよう
<?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桁でフォーマットする
?>このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: PHPオンラインコンパイラ