PHPのジェネレータを使った処理サンプル (yield構文)


※メモ程度のコードです。

ジェネレータ
(PHP 5 >= 5.5.0, PHP 7)

概略

  • PHP5.5から導入された
  • yieldreturn の代わりに記述することでループの中で都度値を返却できる
  • ある共通処理を関数化した際、配列Aを配列Bに代入してゴニョゴニョ・・など2つ配列を作ることでメモリを食ってしまうのをジェネレータ構文を使うことで改善できる(ことがある)

構文

generator.php

<?php

echo "-------basic_sample_1--------\n";

/*ベーシックなサンプル その1
1から3までの数値を出力する*/
function basic_sample_1 ()
{
    $i = 0; //初回は 0
    // yieldは終了ではなく保存ポイント

    yield $i; //保存ポイント! 値を一旦戻す 0
    //一回目ここまで ↑

    //二回目ここから ↓
    $i++; //2回目 プラス 1
    yield $i; //保存ポイント! 値を一旦戻す 1
    //二回目ここまで ↑

    //三回目ここから ↓
    $i++; //2回目 プラス 1
    yield $i; //保存ポイント! 値を一旦戻す 2
    //三回目ここまで ↑

}
// 前回の終了位置から、実行を再開できる

//ジェネレーターオブジェクトを生成する
$generator = basic_sample_1();

foreach ($generator as $x) {
    // yield文の引数($i)がループ変数になる
    var_dump($x);
}
/*
結果
int(0)
int(1)
int(2)
*/

echo "-------while_sample--------\n";

/* while文を用いたサンプル
1から10までの数値を順に出力する*/
function while_sample()
{
    $i = 0;
    //ジェネレータ側では終了条件は指定しない
    while(true) {
        $i++;
        yield $i; //保存ポイント! 値を一旦戻す
    }
}

$generator = while_sample();

foreach($generator as $x) {
    var_dump($x);
    //終了条件は呼び出し側で指定する
    if ($x >= 10) {
        break;
    }
}
/*
結果
int(1)
int(2)
int(3)
int(4)
int(5)
int(6)
int(7)
int(8)
int(9)
int(10)
*/

echo "-------generator recursive--------\n";

/*
ジェネレータからジェネレータを呼ぶ例
CSVファイルに見立てた配列を一行ずつフィールドも分けた状態で配列に格納する
*/

$file = array(
    'john,banana,water',
    'mike,apple,milk',
    'fredy,orange,coffee',
);

//1行ずつ読み込み
function each_record($file)
{
    foreach ($file as $v) {
        yield $v;
    }
}

//1行を更にカンマで分ける
function each_field($generator)
{
    foreach ($generator as $v) {
        $field_array = explode(',', $v);
        yield $field_array;
    }
}

$generator = each_record($file);
$generator = each_field($generator);

foreach($generator as $v) {
    $new_array[] = $v;
}

var_dump($new_array);

/*
結果
array(3) {
  [0]=>
  array(3) {
    [0]=>
    string(4) "john"
    [1]=>
    string(6) "banana"
    [2]=>
    string(5) "water"
  }
  [1]=>
  array(3) {
    [0]=>
    string(4) "mike"
    [1]=>
    string(5) "apple"
    [2]=>
    string(4) "milk"
  }
  [2]=>
  array(3) {
    [0]=>
    string(5) "fredy"
    [1]=>
    string(6) "orange"
    [2]=>
    string(6) "coffee"
  }
}
*/

echo "-------merge using generator--------\n";

// この配列が巨大だった場合、単純にforeachで回すとメモリを食う
$arr1 = array(
  array('hoge1' => 'fuga1'),
  array('hoge2' => 'fuga2'),
  array('hoge3' => 'fuga3'),
  array('hoge4' => 'fuga4'),
);

$arr2 = array(
  array('foo1' => 'bar1'),
  array('foo2' => 'bar2'),
  array('foo3' => 'bar3'),
  array('foo4' => 'bar4'),
);

function arr1_parser($arr1)
{
    foreach ($arr1 as $value) {
        yield $value;
    }
}

$generator = arr1_parser($arr1);

foreach ($generator as $key => $value) {
    var_dump(array_merge($value, $arr2[$key]));
}

/*
結果
array(2) {
  ["hoge1"]=>
  string(5) "fuga1"
  ["foo1"]=>
  string(4) "bar1"
}
array(2) {
  ["hoge2"]=>
  string(5) "fuga2"
  ["foo2"]=>
  string(4) "bar2"
}
array(2) {
  ["hoge3"]=>
  string(5) "fuga3"
  ["foo3"]=>
  string(4) "bar3"
}
array(2) {
  ["hoge4"]=>
  string(5) "fuga4"
  ["foo4"]=>
  string(4) "bar4"
}

*/