3

这个 PyCon 演讲中,Jack Diederich 展示了康威生命游戏的这个“简单”实现。我对 GoL 或半高级 Python 都不是很熟悉,但代码似乎很容易掌握,如果不是因为两件事:

  1. 的使用yield。之前看到过使用yield创建生成器,但是连续8个是新的……是返回一个8个生成器的列表,还是这个东西是怎么工作的?
  2. 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
4

3 回答 3

11

生成器根据两个原则运行:每次yield遇到语句时它们都会产生一个值,除非它被迭代,否则它们的代码会暂停

不管yield生成器中使用了多少语句,代码仍然以正常的 python 顺序运行。在这种情况下,没有循环,只是一系列yield语句,所以每次生成器前进时,python都会执行下一行,这是另一个yield语句。

生成器会发生什么neighbors

  1. 生成器总是开始暂停,所以调用neighbors(position)会返回一个还没有做任何事情的生成器。

  2. 当它被高级(next()被调用)时,代码一直运行到第一个yield语句。首先x, y = point执行,然后x + 1, y计算并生成。代码再次暂停。

  3. 再次前进时,代码将一直运行,直到遇到 yield一条语句。它产生x - 1, y.

  4. 等,直到功能完成。

set(itertools.chain(*map(neighbors, board)))行执行以下操作:

  1. map(neighbors, board)board为序列中的每个位置生成一个迭代器。它只是简单地循环,调用neighbors每个值,并返回一个新的结果序列。每个neighbors()函数都返回一个生成器。

  2. *parameter语法将parameter序列扩展为参数列表,就好像调用函数时将每个元素parameter作为单独的位置参数代替。param = [1, 2, 3]; foo(*param)将转换为foo(1, 2, 3).

    itertools.chain(*map(..))获取地图生成的每个生成器,并将其作为一系列位置参数应用于itertools.chain(). 循环链的输出意味着每个板位置的每个生成器都按顺序迭代一次。

  3. 所有生成的位置都添加到一个集合中,基本上删除了重复项

您可以将代码扩展为:

positions = set()
for board_position in board:
    for neighbor in neighbors(board):
        positions.add(neighbor)

在 python 3 中,该行仍然可以通过使用itertools.chain.from_iterable()来更有效地表达,因为map()在 Python 3 中也是一个生成器;.from_iterable()不会强制map()扩展,而是会map()根据需要一一循环遍历结果。

于 2013-02-27T11:51:59.853 回答
1

哇,这是一个很好的实现,感谢发布!

对于yield,没有什么可以添加到 Martijn 的答案中。

至于star:map返回一个生成器或一个列表(取决于python 2或3),这个列表的每个项目都是一个生成器(来自neighbors),所以我们有一个生成器列表。

chain接受许多可迭代的参数并将它们链接起来,这意味着它在依次迭代所有参数时返回一个可迭代的。

因为我们有一个生成器列表,并且chain需要许多参数,所以我们使用星号将生成器列表转换为参数。我们可以对chain.from_iterable.

于 2013-02-27T11:59:47.370 回答
0

它只是返回所有单元格邻居的元组。如果您确实了解生成器的作用,那么很明显在处理大量数据时使用生成器是一种很好的做法。您不需要将所有这些都存储在内存中,仅在需要时才计算它。

于 2013-02-27T11:53:36.927 回答