回答:
yield
?yield
キーワードジェネレータ関数からのデータを返します:
ジェネレーター関数の中心はyieldキーワードです。最も単純な形式では、yieldステートメントはreturnステートメントによく似ていますが、関数の実行を停止して戻るのではなく、yieldは代わりに、ジェネレーターをループするコードに値を提供し、ジェネレーター関数の実行を一時停止します。
ジェネレータ関数は、イテレータを書くための効果的でコンパクトで効率的な方法です。それはあなたがそれをループしている間に値を計算して返す関数(あなたのxrange
)を定義することを可能にします:
foreach (xrange(1, 10) as $key => $value) {
echo "$key => $value", PHP_EOL;
}
これにより、次の出力が作成されます。
0 => 1
1 => 2
…
9 => 10
を使用$key
してを制御することもできますforeach
yield $someKey => $someValue;
ジェネレータ関数では、$someKey
あなたが現れる好きである$key
と$someValue
における値です$val
。質問の例では$i
です。
ここで、なぜPHPのネイティブrange
関数を使用してその出力を実現していないのか不思議に思うかもしれません。そして、そうです。出力は同じになります。違いはそこにたどり着く方法です。
私たちが使用する場合はrange
PHPを、それを実行し、メモリ内の数字の配列全体を作成しますreturn
その配列全体にforeach
それと出力値の上に移動しますループ。つまり、foreach
はアレイ自体に作用します。range
機能とforeach
一度だけ「話」。郵便でパッケージを受け取るようなものだと考えてください。配達担当者がパッケージを渡して出発します。次に、パッケージ全体を展開し、そこにあるものをすべて取り出します。
ジェネレーター関数を使用すると、PHPは関数にステップインし、最後またはyield
キーワードに到達するまで関数を実行します。と出会うと、yield
そのときの値が何であれ、外側のループに返されます。次に、ジェネレータ関数に戻り、生成された場所から続行します。あなたxrange
がfor
ループを保持しているので、ループは実行され、$max
到達するまで生成されます。それとforeach
ピンポンをするジェネレーターのように考えてください。
明らかに、ジェネレーターを使用してメモリ制限を回避できます。環境によっては、range(1, 1000000)
スクリプトを実行すると致命的なエラーが発生しますが、ジェネレーターでは同じように機能します。またはウィキペディアがそれを置くように:
ジェネレータは生成された値をオンデマンドでのみ計算するので、高価であるか、一度に計算することが不可能であるシーケンスを表すのに役立ちます。これらには、たとえば無限シーケンスやライブデータストリームが含まれます。
ジェネレータもかなり高速であるはずです。しかし、私たちが速く話をしているとき、私たちは通常非常に少ない数で話をしていることを覚えておいてください。したがって、実行してすべてのコードを変更してジェネレーターを使用する前に、ベンチマークを行って、それが意味のある場所を確認してください。
ジェネレータのもう1つの使用例は、非同期コルーチンです。yield
キーワードは、値を返しませんが、それはまた、それらを受け入れます。詳細については、下記の2つの優れたブログ投稿を参照してください。
yield
か?ジェネレーターはPHP 5.5で導入されました。yield
そのバージョンの前に使用しようとすると、キーワードに続くコードに応じて、さまざまな解析エラーが発生します。したがって、そのコードから解析エラーが発生した場合は、PHPを更新してください。
return range(1,100000000)
して for ($i=0; $i<100000000; $i++) yield $i
この関数はyieldを使用しています:
function a($items) {
foreach ($items as $item) {
yield $item + 1;
}
}
これとほぼ同じですが、次の点が異なります。
function b($items) {
$result = [];
foreach ($items as $item) {
$result[] = $item + 1;
}
return $result;
}
唯一の違いはa()
、ジェネレータとb()
単純な配列を返すことです。両方で繰り返すことができます。
また、最初のものは完全な配列を割り当てないため、メモリの要求が少なくなります。
簡単な例
<?php
echo '#start main# ';
function a(){
echo '{start[';
for($i=1; $i<=9; $i++)
yield $i;
echo ']end} ';
}
foreach(a() as $v)
echo $v.',';
echo '#end main#';
?>
出力
#start main# {start[1,2,3,4,5,6,7,8,9,]end} #end main#
高度な例
<?php
echo '#start main# ';
function a(){
echo '{start[';
for($i=1; $i<=9; $i++)
yield $i;
echo ']end} ';
}
foreach(a() as $k => $v){
if($k === 5)
break;
echo $k.'=>'.$v.',';
}
echo '#end main#';
?>
出力
#start main# {start[0=>1,1=>2,2=>3,3=>4,4=>5,#end main#
yield
キーワードは、PHP 5.5の「ジェネレータ」の定義に役立ちます。では、ジェネレーターとは何ですか?
php.netから:
ジェネレーターは、Iteratorインターフェースを実装するクラスを実装するオーバーヘッドや複雑さなしに、単純なイテレーターを実装する簡単な方法を提供します。
ジェネレーターを使用すると、メモリ内に配列を構築する必要なく、foreachを使用して一連のデータを反復するコードを記述できます。これにより、メモリ制限を超えたり、生成にかなりの処理時間が必要になる場合があります。代わりに、通常の関数と同じジェネレーター関数を書くことができます。ただし、1回返すのではなく、ジェネレーターは、反復する値を提供するために必要な回数だけ生成することができます。
この場所から:ジェネレータ=ジェネレータ、他の関数(単なる関数)=関数。
したがって、次の場合に役立ちます。
単純なこと(または単純なこと)を行う必要があります。
ジェネレータは、Iteratorインターフェースを実装するよりもはるかに単純です。一方、当然ながら、ジェネレーターの機能は低下します。それらを比較します。
大量のデータを生成する必要があります-メモリを節約します。
実際にメモリを節約するために、ループの反復ごとに関数を介して必要なデータを生成し、反復後にガベージを利用することができます。したがって、ここでの主なポイントは、明確なコードとおそらくパフォーマンスです。あなたのニーズに最適なものをご覧ください。
中間値に依存するシーケンスを生成する必要があります。
これは以前の考えを拡張したものです。ジェネレーターは、関数と比較して物事を簡単にすることができます。フィボナッチの例を確認し、ジェネレータなしでシーケンスを作成してみてください。また、この場合、ジェネレーターはより高速に動作できます。これは、少なくともローカル変数に中間値を格納するためです。
パフォーマンスを改善する必要があります。
場合によっては、機能してから機能する方が速くなります(前の利点を参照)。
を使用yield
すると、単一の関数で複数のタスク間のブレークポイントを簡単に説明できます。それだけです。特別なことは何もありません。
$closure = function ($injected1, $injected2, ...){
$returned = array();
//task1 on $injected1
$returned[] = $returned1;
//I need a breakpoint here!!!!!!!!!!!!!!!!!!!!!!!!!
//task2 on $injected2
$returned[] = $returned2;
//...
return $returned;
};
$returned = $closure($injected1, $injected2, ...);
task1とtask2は関連性が高いが、何か他のことを行うにはそれらの間にブレークポイントが必要な場合:
コードを多くのクロージャーに分割したり、他のコードと混合したり、コールバックを使用したりする必要がないため、ジェネレーターが最適なソリューションです。yield
ブレークポイントを追加するだけで、そこから続行できます。準備ができたらブレークポイント。
ジェネレータなしでブレークポイントを追加します。
$closure1 = function ($injected1){
//task1 on $injected1
return $returned1;
};
$closure2 = function ($injected2){
//task2 on $injected2
return $returned1;
};
//...
$returned1 = $closure1($injected1);
//breakpoint between task1 and task2
$returned2 = $closure2($injected2);
//...
ジェネレーターでブレークポイントを追加する
$closure = function (){
$injected1 = yield;
//task1 on $injected1
$injected2 = (yield($returned1));
//task2 on $injected2
$injected3 = (yield($returned2));
//...
yield($returnedN);
};
$generator = $closure();
$returned1 = $generator->send($injected1);
//breakpoint between task1 and task2
$returned2 = $generator->send($injected2);
//...
$returnedN = $generator->send($injectedN);
注:ジェネレーターは間違いやすいので、実装する前に必ずユニットテストを記述してください。 注2:無限ループでジェネレーターを使用することは、無限長のクロージャーを書くようなものです...
上記の回答はいずれも、非数値のメンバーが配置された大規模な配列を使用した具体的な例を示していません。これは、explode()
大きな.txtファイル(私の使用例では262MB)で生成された配列を使用した例です。
<?php
ini_set('memory_limit','1000M');
echo "Starting memory usage: " . memory_get_usage() . "<br>";
$path = './file.txt';
$content = file_get_contents($path);
foreach(explode("\n", $content) as $ex) {
$ex = trim($ex);
}
echo "Final memory usage: " . memory_get_usage();
出力は次のとおりです。
Starting memory usage: 415160
Final memory usage: 270948256
次のyield
キーワードを使用して、同様のスクリプトと比較します。
<?php
ini_set('memory_limit','1000M');
echo "Starting memory usage: " . memory_get_usage() . "<br>";
function x() {
$path = './file.txt';
$content = file_get_contents($path);
foreach(explode("\n", $content) as $x) {
yield $x;
}
}
foreach(x() as $ex) {
$ex = trim($ex);
}
echo "Final memory usage: " . memory_get_usage();
このスクリプトの出力は次のとおりです。
Starting memory usage: 415152
Final memory usage: 415616
明らかにメモリ使用量の節約はかなりのものでした(ΔMemoryUsage-----> 最初の例では〜270.5 MB、2番目の例では〜450B)。
ここで議論する価値がある興味深い側面は、参照によって生成されます。関数の外に反映されるようにパラメーターを変更する必要があるたびに、このパラメーターを参照で渡す必要があります。これをジェネレーターに適用するに&
は、ジェネレーターの名前と反復で使用される変数にアンパサンドを付加するだけです。
<?php
/**
* Yields by reference.
* @param int $from
*/
function &counter($from) {
while ($from > 0) {
yield $from;
}
}
foreach (counter(100) as &$value) {
$value--;
echo $value . '...';
}
// Output: 99...98...97...96...95...
上記の例は、foreach
ループ内の反復値を変更すると$from
、ジェネレーター内の変数がどのように変更されるかを示しています。これは、ある$from
されて参照することによって得られたため、発電機の名前の前にアンパサンドに。そのため$value
、foreach
ループ$from
内の変数は、ジェネレーター関数内の変数への参照です。
以下のコードは、完全な反復後に完全な配列を返す従来の非ジェネレーターアプローチとは異なり、ジェネレーターを使用すると、完了する前に結果を返す方法を示しています。以下のジェネレータでは、準備ができたときに値が返され、配列が完全に満たされるのを待つ必要はありません。
<?php
function sleepiterate($length) {
for ($i=0; $i < $length; $i++) {
sleep(2);
yield $i;
}
}
foreach (sleepiterate(5) as $i) {
echo $i, PHP_EOL;
}
yeild
たとえば、次のようなソリューションの利点が何であるかについて詳しく説明してください。ideone.com