Ricerca…


Perché usare un generatore?

I generatori sono utili quando è necessario generare una grande raccolta per iterare più tardi. Sono un'alternativa più semplice alla creazione di una classe che implementa un Iterator , che spesso è eccessivo.

Ad esempio, considera la funzione seguente.

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

Tutto ciò che fa questa funzione genera un array riempito con numeri casuali. Per usarlo, potremmo fare randomNumbers(10) , che ci darà una matrice di 10 numeri casuali. Cosa succede se vogliamo generare un milione di numeri casuali? randomNumbers(1000000) lo farà per noi, ma a un costo di memoria. Un milione di interi memorizzati in un array utilizza circa 33 megabyte di memoria.

$startMemory = memory_get_usage();

$randomNumbers = randomNumbers(1000000);

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

Ciò è dovuto all'intero milione di numeri casuali generati e restituiti contemporaneamente, anziché uno alla volta. I generatori sono un modo semplice per risolvere questo problema.

Riscrivere randomNumbers () usando un generatore

La nostra funzione randomNumbers() può essere riscritta per utilizzare un generatore.

<?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";
}

Utilizzando un generatore, non è necessario creare un intero elenco di numeri casuali per tornare dalla funzione, con conseguente riduzione della memoria utilizzata.

Lettura di un file di grandi dimensioni con un generatore

Un caso d'uso comune per i generatori è la lettura di un file dal disco e l'iterazione del suo contenuto. Di seguito è riportata una classe che consente di eseguire l'iterazione su un file CSV. L'utilizzo della memoria per questo script è molto prevedibile e non fluttuerà a seconda delle dimensioni del file 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 parola chiave di rendimento

Un'istruzione yield è simile a un'istruzione return, tranne che invece di arrestare l'esecuzione della funzione e restituirla, yield restituisce invece un oggetto Generator e sospende l'esecuzione della funzione del generatore.

Ecco un esempio della funzione range, scritta come generatore:

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

Puoi vedere che questa funzione restituisce un oggetto generatore controllando l'output di var_dump :

var_dump(gen_one_to_three())

# Outputs:
class Generator (0) {
}

Valori cedevoli

L'oggetto Generator può quindi essere ripetuto come un array.

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

L'esempio sopra uscirà:

1
2
3

Valorizzare i valori con le chiavi

Oltre a produrre valori, puoi anche produrre coppie chiave / valore.

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";
}

L'esempio sopra uscirà:

first: 1
second: 2
third: 3

Utilizzo della funzione send () - per passare valori a un generatore

I generatori sono codificati rapidamente e in molti casi sono un'alternativa sottile alle implementazioni iteratore pesanti. Con l'implementazione rapida arriva un po 'di mancanza di controllo quando un generatore dovrebbe smettere di generare o se dovrebbe generare qualcos'altro. Tuttavia questo può essere ottenuto con l'uso della funzione send() , consentendo alla funzione di richiesta di inviare parametri al generatore dopo ogni 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.
    }
}

Risultante in questa uscita:

inserisci la descrizione dell'immagine qui



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow