Szukaj…


Dlaczego warto korzystać z generatora?

Generatory są przydatne, gdy trzeba wygenerować dużą kolekcję, aby później ją iterować. Są prostszą alternatywą dla stworzenia klasy, która implementuje Iterator , co często jest przesadą.

Rozważmy na przykład poniższą funkcję.

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

Ta funkcja generuje tablicę wypełnioną liczbami losowymi. Aby go użyć, możemy wykonać randomNumbers(10) , co da nam tablicę 10 liczb losowych. Co jeśli chcemy wygenerować milion liczb losowych? randomNumbers(1000000) zrobi to za nas, ale kosztem pamięci. Milion liczb całkowitych przechowywanych w tablicy zużywa około 33 megabajtów pamięci.

$startMemory = memory_get_usage();

$randomNumbers = randomNumbers(1000000);

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

Wynika to z faktu, że cały milion losowych liczb jest generowanych i zwracanych jednocześnie, a nie pojedynczo. Generatory to prosty sposób na rozwiązanie tego problemu.

Ponowne pisanie randomNumbers () za pomocą generatora

Nasza randomNumbers() może zostać przepisana w celu użycia generatora.

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

Za pomocą generatora nie musimy budować całej listy liczb losowych, aby powrócić z funkcji, co prowadzi do znacznie mniejszej ilości pamięci.

Odczytywanie dużego pliku za pomocą generatora

Jednym z powszechnych przypadków użycia generatorów jest odczytywanie pliku z dysku i iterowanie jego zawartości. Poniżej znajduje się klasa, która pozwala na iterację po pliku CSV. Użycie pamięci dla tego skryptu jest bardzo przewidywalne i nie będzie się zmieniać w zależności od rozmiaru pliku 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.
}

Słowo kluczowe wydajności

Instrukcja yield jest podobna do instrukcji return, z tą różnicą, że zamiast zatrzymywać wykonywanie funkcji i zwracać, wydaj zamiast zwraca obiekt Generator i wstrzymuje wykonywanie funkcji generatora.

Oto przykład funkcji zakresu zapisanej jako generator:

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

Możesz zobaczyć, że ta funkcja zwraca obiekt Generator , sprawdzając dane wyjściowe var_dump :

var_dump(gen_one_to_three())

# Outputs:
class Generator (0) {
}

Wartości wydajności

Obiekt Generator można następnie iterować jak tablicę.

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

Powyższy przykład wyświetli:

1
2
3

Uzyskiwanie wartości za pomocą kluczy

Oprócz uzyskiwania wartości, możesz także uzyskiwać pary klucz / wartość.

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

Powyższy przykład wyświetli:

first: 1
second: 2
third: 3

Korzystanie z funkcji send () - do przekazywania wartości do generatora

Generatory są kodowane szybko, aw wielu przypadkach stanowią wąską alternatywę dla ciężkich implementacji iteratorów. Dzięki szybkiej implementacji pojawia się niewielki brak kontroli, kiedy generator powinien przestać generować lub czy powinien wygenerować coś innego. Można to jednak osiągnąć za pomocą funkcji send() , która umożliwia funkcji wysyłającej wysyłanie parametrów do generatora po każdej pętli.

//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.
    }
}

Wynikające z tego wyniku:

wprowadź opis zdjęcia tutaj



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow