发电机和阵列之间有什么区别?

45
今天PHP团队发布了PHP 5.5.0版本,其中包含对生成器的支持。阅读文档时,我注意到它可以像处理数组一样处理生成器。
PHP团队生成器示例:
// Only PHP 5.5
function gen_one_to_three() {
    for ($i = 1; $i <= 3; $i++) {
        // Note that $i is preserved between yields.
        yield $i;
    }
}

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

Result:

1
2
3

但是我可以使用数组来做同样的事情。而且我仍然可以保持与早期版本的PHP兼容。
来看一下:
// Compatible with 4.4.9!
function gen_one_to_three() {
    $results = array();
    for ($i = 1; $i <= 3; $i++) {
        $results[] = $i;
    }
    
    return $results;
}

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

所以问题是:这个新功能的存在的目的是什么?我已经玩过所有的文档示例,而没有使用新功能,而是用数组替代它。
有人能给出一个好的解释,也许还有一个不一定是在旧版本中不可能的例子,但使用生成器可以在开发中有所帮助吗?

3
使用协程实现协作式多任务处理(在PHP中!)本文介绍了如何在PHP中使用协程实现协作式的多任务处理。通过使用协程,可以将长时间运行的任务分解成多个小任务,从而实现对任务的协作式调度与执行。这种方法不仅能够提高程序的性能,还能够增加代码的可读性和可维护性。具体而言,本文首先介绍了协程的概念和基本用法,然后给出了一个使用协程实现异步IO操作的示例,最后讨论了如何使用协程实现协作式的多任务处理。通过本文的介绍,读者可以了解到协程的优势与应用场景,并学会如何在PHP中使用协程来实现高效的多任务处理。 - Baba
7
数组是元素的具体序列。生成器是一种逐个生成序列元素的函数。将生成器的结果立即转换成数组(强制实体化,等同于一开始就创建数组!)并不太有趣 - 用生成器作为才是它开始发挥作用的地方。只计算需要的元素数量。考虑生成N个斐波那契数.. - user2246674
3
如需更详细的功能说明,请查看RFC:https://wiki.php.net/rfc/generators - NikiC
1
与其他语言相同:迭代器和生成器有什么区别?有哪些有用或有趣的无限生成器?,以及http://en.wikipedia.org/wiki/Generator_(computer_programming)。 - mario
@user2246674 - 我已经在我的回答中添加了一个斐波那契数列的例子 :) - Mark Baker
4个回答

67

区别在于效率。例如,许多语言除了PHP之外还包括两个range函数,分别是range()xrange()。这是使用生成器的一个很好的例子。让我们构建自己的:

function range($start, $end) {
    $array = array();
    for ($i = $start; $i <= $end; $i++) {
        $array[] = $i;
    }
    return $array;
}

现在这很简单明了。但是对于大范围的情况,它需要大量的内存。如果我们尝试使用 $start = 0$end = 100000000 运行它,我们可能会耗尽内存!

但是如果我们使用一个生成器:

function xrange($start, $end) {
    for ($i = $start; $i <= $end; $i++) {
        yield $i;
    }
}

现在我们使用常量内存,但仍然有一个“数组”(类似的结构),我们可以在同一空间中迭代它(并与其他迭代器一起使用)。

它并不 替换 数组,但它提供了一种有效的方法来避免需要记忆体的情况...

但是它也在生成项方面提供了节省。由于每个结果都会按需生成,您可以延迟执行(获取或计算)每个元素,直到您需要它。因此,例如,如果您需要从数据库提取项目并在每行周围进行复杂处理,则可以使用生成器延迟执行该操作,直到您实际需要该行:

function fetchFromDb($result) {
    while ($row = $result->fetchArray()) {
        $record = doSomeComplexProcessing($row);
        yield $record;
    }
}

如果你只需要前三个结果,那么你只需要处理前三条记录。

更多信息,请参阅我在这个精确主题上撰写的博客文章


14

生成器允许对复杂语句进行惰性求值。这样可以节省内存,因为您不必一次性分配所有内容。

除了两者都是可迭代的,它们几乎没有任何相似之处。一个array是一个数据结构,而生成器不是。


12
一个数组必须在开始循环之前包含您要循环的每个值;生成器会在请求时“即兴创作”每个值,因此需要更少的内存; 一个数组使用它所包含的值,并且必须使用这些值进行预先填充;生成器可以根据特殊条件创建值以供直接使用...例如斐波那契数列或来自非A-Z字母表的字母(由UTF-8数值计算),有效地允许alphaRange('א','ת')。 编辑
function fibonacci($count) {
    $prev = 0;
    $current = 1;

    for ($i = 0; $i < $count; ++$i) {
        yield $prev;
        $next = $prev + $current;
        $prev = $current;
        $current = $next;
    }
}

foreach (fibonacci(48) as $i => $value) {
    echo $i , ' -> ' , $value, PHP_EOL;
}

编辑

仅供娱乐,这是一个生成器,它将以UTF-8字符返回希伯来字母表。

function hebrewAlphabet() {
    $utf8firstCharacter = 1488;
    $utf8lastCharacter = 1514;
    for ($character = $utf8firstCharacter; $character <= $utf8lastCharacter; ++$character) {
        yield html_entity_decode('&#'.$character.';', ENT_NOQUOTES, 'UTF-8');
    };
}

foreach(hebrewAlphabet() as $character) {
    echo $character, ' ';
}

4

就像Python中的一样:

当使用for语句开始对一组项目进行迭代时,生成器将运行。一旦生成器的函数代码到达"yield"语句,生成器将把执行权返回给for循环,并从集合中返回一个新值。生成器函数可以生成任意数量(可能是无限的)的值,在轮流中产生每个值。

...生成器依次执行yield语句,在此期间暂停以将执行权交回主for循环。

-learnpython.org


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接