如何用纯NumPy向量化实现康威生命游戏?
当然可以!完全不用for循环、if语句这类传统控制结构来实现康威生命游戏是完全可行的——而且这其实是很多高性能元胞自动机(CA)实现的核心思路,核心就是用向量化操作和矩阵运算来替代逐细胞的遍历和条件判断。
康威生命游戏的核心是两个步骤:计算每个细胞的活邻居数量,然后根据规则更新细胞状态。这两步都可以用纯矩阵/数组操作完成,完全不需要手写循环或条件判断。
1. 邻域计数:用批量网格操作替代逐细胞遍历
你之前写的neighbors函数是逐个细胞计算邻域,但我们可以对整个网格做平移叠加来一次性得到所有细胞的邻居数:比如用NumPy的话,我们可以把网格分别向上、向下、向左、向右,以及四个对角线方向平移,然后把这些平移后的网格相加——这样每个位置的和就是对应原细胞的活邻居数量(因为平移后的网格对应原细胞的各个邻域位置)。
举个具体的例子(用NumPy实现):
import numpy as np def count_neighbors(grid): # 对网格进行8个方向的平移,然后相加得到邻居数 return ( np.roll(grid, 1, axis=0) + # 向上平移 np.roll(grid, -1, axis=0) + # 向下平移 np.roll(grid, 1, axis=1) + # 向左平移 np.roll(grid, -1, axis=1) + # 向右平移 np.roll(grid, (1, 1), axis=(0, 1)) + # 左上平移 np.roll(grid, (1, -1), axis=(0, 1)) + # 右上平移 np.roll(grid, (-1, 1), axis=(0, 1)) + # 左下平移 np.roll(grid, (-1, -1), axis=(0, 1)) # 右下平移 )
这里np.roll是循环平移(相当于把网格当成 torus 拓扑,边缘细胞的邻居会从对面过来),如果需要固定边界(比如边缘细胞没有外侧邻居),可以用切片操作来替代:
def count_neighbors_fixed_border(grid): neighbors = np.zeros_like(grid) # 只计算内部细胞的邻居,边缘细胞邻居数为0 neighbors[1:-1, 1:-1] = ( grid[:-2, :-2] + grid[:-2, 1:-1] + grid[:-2, 2:] + grid[1:-1, :-2] + grid[1:-1, 2:] + grid[2:, :-2] + grid[2:, 1:-1] + grid[2:, 2:] ) return neighbors
这两个函数都没有任何循环或条件判断,完全是数组的批量操作。
2. 状态更新:用布尔运算替代if语句
康威的规则可以转化为纯布尔表达式,然后直接转换为整数(1=活,0=死):
- 活细胞存活:邻居数为2或3 →
(grid == 1) & ((neighbors == 2) | (neighbors == 3)) - 死细胞复活:邻居数为3 →
(grid == 0) & (neighbors == 3)
把这两个条件做逻辑或,再转成整数就是新的网格状态:
def update(grid): neighbors = count_neighbors(grid) # 用布尔运算直接生成新状态,完全不用if new_grid = ( ((grid == 1) & ((neighbors == 2) | (neighbors == 3))) | ((grid == 0) & (neighbors == 3)) ).astype(int) return new_grid
完整示例:无控制结构的康威生命游戏
把上面的函数组合起来,就可以实现完整的游戏演化(这里的顶层循环是为了演示多步演化,核心的状态更新逻辑完全没有控制结构):
# 初始化一个随机网格 grid = np.random.randint(0, 2, size=(50, 50)) # 演化50步 for _ in range(50): grid = update(grid) # 这里可以添加可视化代码,比如用matplotlib显示网格
向量化操作的本质是让底层的优化库(比如NumPy依赖的BLAS)来处理循环逻辑,这些库都是用C/汇编实现的,比手写Python循环高效得多。而且我们把逐细胞的条件判断转换成了整个数组的布尔运算,完全避开了if语句的使用。
对于你刚开始研究元胞自动机的需求来说,这种方法能帮你更好地理解CA的“批量”特性——元胞的状态更新是并行的,而向量化操作正好契合这种并行性。
内容的提问来源于stack exchange,提问作者Daniele Grattarola

