PHPのジェネレータ――foreachループで使える値生成のための関数Web業界で働くためのPHP入門(12)(2/3 ページ)

» 2017年10月30日 05時00分 公開

配列とジェネレータ

 ジェネレータが何かを理解できたところで、少しずつ応用させていきます。まず、配列をループ処理する場合を考えます。まずは、下記のloopArray.phpを作成し、実行してください。

<?php
$array = [1, 3, 5, 7];
foreach($array as $value) {
    $ans = $value * 8;
    print($ans."<br>");
}
リスト3 phplesson/chap12/loopArray.php

 実行結果は下記の通りです。

8
24
40
56

 特に説明の必要はないでしょう。2行目で配列を用意し、それを3行目以降でループ処理しています。

 これと同じ処理を、ジェネレータを使って実現できます。下記のloopGenerator.phpを作成し、実行してください。

<?php
function yieldArrayElement()
{
    $array = [1, 3, 5, 7];
    yield $array[0];
    yield $array[1];
    yield $array[2];
    yield $array[3];
}
 
foreach(yieldArrayElement() as $value) {
    $ans = $value * 8;
    print($ans."<br>");
}
リスト4 phplesson/chap12/loopGenerator.php

 実行結果はリスト3と同じです。リスト4は配列を直接ループ処理しています。

図4 配列を直接ループ処理

 一方、リスト4では配列を、ジェネレータを使って1つずつyieldしながら処理しています。

図5 配列をyieldしながらループ処理

 これら、2つは同じ結果を得られます。

 さらに、リスト4のyieldArrayElement()では配列内の要素を、インデックスを使ってyieldしていますが、通常このような記述はせずにループで処理します。リスト4の改良版である下記のloopGenerator2.phpを作成し、実行してください。

<?php
function yieldArrayElement()
{
    $array = [1, 3, 5, 7];
    foreach($array as $element) {
        yield $element;
    }
}
 
foreach(yieldArrayElement() as $value) {
    $ans = $value * 8;
    print($ans."<br>");
}
リスト5 phplesson/chap12/loopGenerator2.php

 実行結果はリスト4と同じです。

 ジェネレータ関数yieldArrayElement()内で

yield $array[0];

となっていたものが、

yield $element;

とループ処理に置き換わっただけですが、このように記述するのが普通です。

ジェネレータを使うメリット

 このように、配列のループがジェネレータを使って代用できることが理解できたと思いますが、そもそも、この方式のメリットが分からないと思います。ソースコードでいえば、リスト5の方が圧倒的に簡素です。では、配列そのものが、リスト5のように固定データではなく、ループで生成するとしたらどうなるでしょうか。

 例えば、指定の個数だけ1〜10の乱数が格納された配列を用意し、その配列を表示させながら合計値を計算する処理を考えます。下記のuseRandLoop.phpのようになります。このファイルを作成し、実行してください。なお、ここでは、配列の要素数として10個を指定しています。

<?php
function createRandArray(int $count): array
{
    $array = [];
    for($i = 1; $i <= $count; $i++) {
        $array[] = rand(1, 10);
    }
    return $array;
}
 
$sum = 0;
foreach(createRandArray(10) as $value) {
    print("現在の乱数: ".$value."<br>");
    $sum += $value;
}
print("乱数の合計: ".$sum);
リスト6 phplesson/chap12/useRandLoop.php

 実行結果は下記の通りです。なお、乱数を使用するので、実行結果は毎回変わります。

現在の乱数: 1
現在の乱数: 10
現在の乱数: 2
現在の乱数: 10
現在の乱数: 8
現在の乱数: 8
現在の乱数: 6
現在の乱数: 3
現在の乱数: 4
現在の乱数: 6
乱数の合計: 58

 ここでは通常の関数createRandArray()を使って乱数が格納された配列を生成しています。その配列を使って、表示、および合計値の計算を行うループ処理を11行目以降で行っています。このソースコードの問題点は、createRandArray()関数でいったん配列を生成していることです。生成された配列はその個数分だけメモリを必要とします。ここでは要素数が10コの配列なので問題ないですが、要素数が100万個となると、100MBを超えるメモリを使うことになります。

 ところが、この問題点はジェネレータを使うことで解決できます。リスト6と同じ処理を、ジェネレータを使って実現する下記のuseRandGenerator.phpを作成し、実行してください。

<?php
function randGenerator($count)
{
    for($i = 1; $i <= $count; $i++) {
        yield rand(1, 10);
    }
}
 
$sum = 0;
foreach(randGenerator(10) as $value) {
    print("現在の乱数: ".$value."<br>");
    $sum += $value;
}
print("乱数の合計: ".$sum);
リスト7 phplesson/chap12/useRandGenerator.php

 実行結果は、乱数を使っているので値は変わっていますが、リスト6と同じようなものになります。

 ここでのポイントは、randGenerator()ジェネレータ関数と、その内部で値をyieldしている5行目です。このジェネレータ関数では、その場で乱数を生成し、それをyieldしています。こうすることで、全部の値が格納された配列を用意する必要がなく、値がループされるたびに生成されるのでメモリの節約になります。これは、生成する値が多ければ多いほど、その恩恵にあずかれます。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。