Buscar..


¿Por qué usar un generador?

Los generadores son útiles cuando necesitas generar una colección grande para iterar más tarde. Son una alternativa más simple a la creación de una clase que implementa un iterador , que a menudo es una exageración.

Por ejemplo, considere la siguiente función.

function randomNumbers(int $length)
{
    $array = [];
    
    for ($i = 0; $i < $length; $i++) {
        $array[] = mt_rand(1, 10);
    }
    
    return $array;
}

Todo lo que hace esta función es generar una matriz que está llena de números aleatorios. Para usarlo, podríamos hacer randomNumbers(10) , lo que nos dará una matriz de 10 números aleatorios. ¿Y si queremos generar un millón de números aleatorios? randomNumbers(1000000) lo hará por nosotros, pero a un costo de memoria. Un millón de enteros almacenados en una matriz utiliza aproximadamente 33 megabytes de memoria.

$startMemory = memory_get_usage();

$randomNumbers = randomNumbers(1000000);

echo memory_get_usage() - $startMemory, ' bytes';

Esto se debe a que un millón de números aleatorios completos se generan y se devuelven a la vez, en lugar de uno a la vez. Los generadores son una manera fácil de resolver este problema.

Reescribiendo números aleatorios () usando un generador

Nuestra función randomNumbers() puede reescribirse para usar un generador.

<?php

function randomNumbers(int $length)
{
    for ($i = 0; $i < $length; $i++) {
        // yield tells the PHP interpreter that this value
        // should be the one used in the current iteration.
        yield mt_rand(1, 10);
    }
}

foreach (randomNumbers(10) as $number) {
    echo "$number\n";
}

Usando un generador, no tenemos que construir una lista completa de números aleatorios para regresar de la función, lo que lleva a que se use mucho menos memoria.

Leyendo un archivo grande con un generador.

Un caso de uso común para los generadores es leer un archivo del disco e iterar sobre su contenido. A continuación se muestra una clase que le permite iterar sobre un archivo CSV. El uso de memoria para este script es muy predecible y no fluctuará dependiendo del tamaño del archivo CSV.

<?php

class CsvReader
{
    protected $file;
 
    public function __construct($filePath) {
        $this->file = fopen($filePath, 'r');
    }
 
    public function rows()
    {
        while (!feof($this->file)) {
            $row = fgetcsv($this->file, 4096);
            
            yield $row;
        }
        
        return;
    }
}
 
$csv = new CsvReader('/path/to/huge/csv/file.csv');

foreach ($csv->rows() as $row) {
    // Do something with the CSV row.
}

La palabra clave de rendimiento

Una declaración de yield es similar a una declaración de retorno, excepto que en lugar de detener la ejecución de la función y devolverla, el rendimiento devuelve un objeto Generador y detiene la ejecución de la función del generador.

Aquí hay un ejemplo de la función de rango, escrita como un generador:

function gen_one_to_three() {
    for ($i = 1; $i <= 3; $i++) {
        // Note that $i is preserved between yields.
        yield $i;
    }
}

Puede ver que esta función devuelve un objeto Generador al inspeccionar la salida de var_dump :

var_dump(gen_one_to_three())

# Outputs:
class Generator (0) {
}

Valores de rendimiento

El objeto Generador se puede iterar sobre una matriz.

foreach (gen_one_to_three() as $value) {
    echo "$value\n";
}

El ejemplo anterior dará como resultado:

1
2
3

Rendimiento de valores con claves

Además de generar valores, también puede generar pares clave / valor.

function gen_one_to_three() {
    $keys = ["first", "second", "third"];

    for ($i = 1; $i <= 3; $i++) {
        // Note that $i is preserved between yields.
        yield $keys[$i - 1] => $i;
    }
}

foreach (gen_one_to_three() as $key => $value) {
    echo "$key: $value\n";
}

El ejemplo anterior dará como resultado:

first: 1
second: 2
third: 3

Uso de la función send () para pasar valores a un generador

Los generadores están codificados rápidamente y, en muchos casos, son una alternativa delgada a las implementaciones de iteradores pesados. Con la implementación rápida viene una pequeña falta de control cuando un generador debe dejar de generar o si debe generar algo más. Sin embargo, esto se puede lograr con el uso de la función send() , lo que permite a la función de solicitud enviar parámetros al generador después de cada ciclo.

//Imagining accessing a large amount of data from a server, here is the generator for this:
function generateDataFromServerDemo()
{
    $indexCurrentRun = 0; //In this example in place of data from the server, I just send feedback everytime a loop ran through.

    $timeout = false;
    while (!$timeout)
    {
        $timeout = yield $indexCurrentRun; // Values are passed to caller. The next time the generator is called, it will start at this statement. If send() is used, $timeout will take this value.
        $indexCurrentRun++;
    }

    yield 'X of bytes are missing. </br>';
}

// Start using the generator
$generatorDataFromServer = generateDataFromServerDemo ();
foreach($generatorDataFromServer as $numberOfRuns)
{
    if ($numberOfRuns < 10)
    {
        echo $numberOfRuns . "</br>";
    }
    else
    {
        $generatorDataFromServer->send(true); //sending data to the generator
        echo $generatorDataFromServer->current(); //accessing the latest element (hinting how many bytes are still missing.
    }
}

Resultando en esta salida:

introduzca la descripción de la imagen aquí



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow