在this PyCon talk中,Jack Diederich展示了Conway's Game of Life的这种“简单”实现。我对GoL或半高级Python并不熟悉,但代码似乎很容易掌握,如果不是两件事:
yield
。我之前看过使用yield来创建生成器,但是它们中的8个是新的......它是否返回了8个生成器的列表,或者这个是如何工作的?set(itertools.chain(*map(neighbors, board)))
。明星解压缩结果列表(?),将邻居应用到董事会,并......我的思绪刚刚爆炸。是否有人试图为程序员解释这两个部分,这些程序员习惯于使用map,filter和reduce来篡改一些python代码,但是这不是每天都在使用Python? : - )
import itertools
def neighbors(point):
x, y = point
yield x + 1, y
yield x - 1, y
yield x, y + 1
yield x, y - 1
yield x + 1, y + 1
yield x + 1, y - 1
yield x - 1, y + 1
yield x - 1, y - 1
def advance(board):
newstate = set()
recalc = board | set(itertools.chain(*map(neighbors, board)))
for point in recalc:
count = sum((neigh in board) for neigh in neighbors(point))
if count == 3 or (count == 2 and point in board):
newstate.add(point)
return newstate
glider = set([(0,0), (1,0), (2, 0), (0,1), (1,2)])
for i in range(1000):
glider = advance(glider)
print glider
答案 0 :(得分:11)
生成器基于两个原则运行:每次遇到yield
语句时它们都会产生一个值,除非它被迭代,否则它们的代码暂停。
在生成器中使用了多少yield
个语句并不重要,代码仍然以正常的python顺序运行。在这种情况下,没有循环,只有一系列yield
语句,所以每次生成器进阶时,python都会执行下一行,这是另一个yield
语句。
neighbors
生成器会发生什么:
生成器始终开始暂停,因此调用neighbors(position)
会返回尚未执行任何操作的生成器。
当它处于高级状态(在其上调用next()
)时,代码将一直运行到第一个yield
语句。首先执行x, y = point
,然后计算并生成x + 1, y
。代码再次暂停。
再次提升时,代码会一直运行,直到遇到 next yield
语句。它产生x - 1, y
。
等。直到功能完成。
set(itertools.chain(*map(neighbors, board)))
行确实:
map(neighbors, board)
为board
序列中的每个位置生成一个迭代器。它只是循环遍历,在每个值上调用neighbors
,并返回一个新的结果序列。每个neighbors()
函数都返回一个生成器。
*parameter
语法将parameter
序列扩展为参数列表,就好像使用parameter
中的每个元素作为单独的位置参数调用函数一样。 param = [1, 2, 3]; foo(*param)
会转换为foo(1, 2, 3)
。
itertools.chain(*map(..))
获取地图生成的每个生成器,并将其作为一系列位置参数应用于itertools.chain()
。循环链的输出意味着每个板位置的每个发生器按顺序迭代一次。
所有生成的位置都会添加到一个集合中,基本上会删除重复项
您可以将代码扩展为:
positions = set()
for board_position in board:
for neighbor in neighbors(board):
positions.add(neighbor)
在python 3中,通过使用itertools.chain.from_iterable()
代替,可以更有效地表达该行,因为Python 3中的map()
也是生成器; .from_iterable()
不会强制map()
展开,而是会根据需要逐个循环map()
结果。
答案 1 :(得分:1)
对于yield
,Martijn的答案没有任何补充。
至于星号:map
返回一个生成器或列表(取决于python 2或3),此列表中的每个项目都是一个生成器(来自neighbors
),所以我们有发电机清单。
chain
接受许多可迭代的参数并将它们链接起来,这意味着它会返回一个迭代,同时依次遍历所有参数。
因为我们有一个生成器列表,而chain
有很多参数,所以我们使用一个星号将生成器列表转换为参数。我们可以使用chain.from_iterable
完成相同的工作。
答案 2 :(得分:0)
它只返回所有单元格邻居的元组。如果您确实了解了生成器的作用,很明显在处理大量数据时使用生成器是一种很好的做法。你不需要将所有这些存储在内存中,只有在需要时才计算它。