PHP面试一战到底
上QQ阅读APP看书,第一时间看更新

4.4 生成器(Generator)与yield

前面在1.1.8小节讲述过迭代器。一个迭代器是这样一个容器,它实现了Traversable接口,从而能够遍历容器中的内部元素。PHP中最常见的迭代器是数组,能使用foreach来遍历所有的元素。

与迭代器密切相关的概念是生成器(Generator)和yield。

4.4.1 生成器

为了解释生成器,我们先看一个现实中的例子。

假设某工厂有5000名工人,为解决就餐问题开设了食堂,每餐每人有1碗米饭1盘菜。工人打饭时有两个方案:

方案1:食堂工作人员把5000份套餐全部打包好,放在桌子上,依次发给工人。

方案2:工人排队,食堂工作人员依次给工人打饭。

现实生活中第1种方案并不常见,因为事先把所有套餐全部打包好,需要占用餐厅5000个位置来放置这些饭盒,很浪费空间;而采用第2种方案,米饭和菜只需分别放在一个大桶里,随用随取即可。

将这个例子放到程序里可以有如下表示:

1.数组里有5000个元素

2.采用自定义的生成函数生成

(源码文件:ch04/generator.php)

采用memory_get_usage方法打印出两种方案所需的内存使用量:

方案1:968912

方案2:226504

可以看到方案2的内存使用量仅是方案1的1/4。方案2也有其他优点:实现简单,1个人就可以打饭;方案1装满5000份套餐所需时间太长,后面装好前面就凉了,方案2随用随取。

生成器是用来生成迭代器的函数,其优点有以下三个:

● 实现简单。

● 避免分配大块内存,防止程序超过内存限制。

● 避免生成迭代器的执行时间过长。

4.4.2 yield

yield应用于生成器函数里,类似于return,但略有不同:

● yield可以有多个。

● yield会记住上次返回的值,下次调用时会返回下一个yield。

例如以下示例中,有3个yield,每次遍历时会依次返回1,2,3的值,从而实现遍历。

(源码文件:ch04/yield_demo)

4.4.3 生成器的设计

Generator可以生成一个可迭代的容器,显然这个容器的容量是有限的。那么设计生成器时,就引出一个问题:是由Generator控制数目,还是在循环中控制呢?这里分享一个规则:

● 规则1:当数据有一定规则可循时,可以由Generator控制数目。例如生成奇数,计算阶乘等。

● 规则2:当数据无规则可循或随机时,一般在循环中控制。

为了便于理解,我们实现两个Generator。

1.生成奇数

编写一个函数,当输入n时,输出n个奇数。

程序代码如下:(源码文件:ch04/generator_odd.php)

2.生成uuid

uuid是通用唯一识别码(Universally Unique Identifier),理论上每个uuid都是全局唯一的,这在生成不重复的标识符时非常有用,如订单号、物流号。按照概率论计算,一个人每年被陨石击中的概率大概是170亿分之一,而两个uuid重复的概率比这还小。

程序示例如下:(源码文件:ch04/generator_uuid.php)

这个例子中,采用外部变量$num来控制遍历的数目,一旦达到数量限制,遍历过程会被break,从而控制了循环的流程。

4.4.4 面试题:用yield实现斐波那契数列

题目描述:输入n,返回斐波那契数列的前n个数,要求用yield实现。

解答:斐波那契数列的每一项都是前两个数之和,例如:

0,1,1,2,3,5,8,13,...

实现代码如下:(源码文件:ch04/yield_fibonacci.php)

引申思考:为什么不用递归实现斐波那契数列呢?

递归实现斐波那契数列的确比较简单,但存在重复计算的问题;而yield实现(这种方法叫动态规划)时,能够暂存子问题的结果,因此效率更高些。