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

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

配列とジェネレータ

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

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

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

8
24
40
56

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

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

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

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

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

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

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

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

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

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

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

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

yield $array[0];

となっていたものが、

yield $element;

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

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

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

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

  1. <?php
  2. function createRandArray(int $count): array
  3. {
  4. $array = [];
  5. for($i = 1; $i <= $count; $i++) {
  6. $array[] = rand(1, 10);
  7. }
  8. return $array;
  9. }
  10. $sum = 0;
  11. foreach(createRandArray(10) as $value) {
  12. print("現在の乱数: ".$value."<br>");
  13. $sum += $value;
  14. }
  15. 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を作成し、実行してください。

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

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

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

Copyright © ITmedia, Inc. All Rights Reserved.

スポンサーからのお知らせPR

HTML5+UX 記事ランキング

本日月間

注目のテーマ

4AI by @IT - AIを作り、動かし、守り、生かす
Microsoft & Windows最前線2025
AI for エンジニアリング
ローコード/ノーコード セントラル by @IT - ITエンジニアがビジネスの中心で活躍する組織へ
Cloud Native Central by @IT - スケーラブルな能力を組織に
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

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

メールマガジン登録

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