如何在Python中同时优雅地遍历多个二维列表?
如果我在制作一个简单的基于网格的游戏,比如说,我可能会有几个二维列表。一个列表可能用来表示地形,另一个列表可能用来表示物体等等。不幸的是,当我需要遍历这些列表,并让一个列表中某个方块的内容影响另一个列表的一部分时,我就得像这样做。
for i in range(len(alist)):
for j in range(len(alist[i])):
if alist[i][j].isWhatever:
blist[i][j].doSomething()
有没有更好的方法来做到这一点呢?
11 个回答
你可以把它们压缩在一起。比如:
for a_row,b_row in zip(alist, blist):
for a_item, b_item in zip(a_row,b_row):
if a_item.isWhatever:
b_item.doSomething()
不过,如果你很少用到 b_item(也就是说 a_item.isWhatever 通常是 False),那么压缩和遍历这些项目的开销可能会比你原来的方法更高。你可以用 itertools.izip 来代替 zip,这样可以减少内存的使用,但如果你不总是需要 b_item,它的速度可能还是会稍微慢一些。
另外,你可以考虑使用三维列表,这样第 i,j 个单元格的地形可以放在 l[i][j][0],物体放在 l[i][j][1],等等。或者你也可以把物体合并,这样就可以用 a[i][j].terrain 和 a[i][j].object 来访问。
[编辑] DzinX 的测试结果 显示,检查 b_item 的额外开销其实并不算太大,相比于重新通过索引查找的性能损失,所以上面提到的(使用 izip)似乎是最快的选择。
我现在也对三维方法进行了快速测试,结果显示它似乎更快,所以如果你能以这种形式存储数据,访问起来可能会更简单、更快速。下面是一个使用三维列表的例子:
# Initialise 3d list:
alist = [ [[A(a_args), B(b_args)] for i in xrange(WIDTH)] for j in xrange(HEIGHT)]
# Process it:
for row in xlist:
for a,b in row:
if a.isWhatever():
b.doSomething()
这是我在使用 1000x1000 数组进行 10 次循环的时间记录,isWhatever 为 true 的各种比例如下:
( Chance isWhatever is True )
Method 100% 50% 10% 1%
3d 3.422 2.151 1.067 0.824
izip 3.647 2.383 1.282 0.985
original 5.422 3.426 1.891 1.534
如果有人对上面提到的解决方案的性能感兴趣,这里是针对4000x4000的网格,从最快到最慢的排序:
- Brian: 1.08秒(经过修改,使用了
izip
而不是zip
) - John: 2.33秒
- DzinX: 2.36秒
- ΤΖΩΤΖΙΟΥ: 2.41秒(但对象初始化花了62秒)
- Eugene: 3.17秒
- Robert: 4.56秒
- Brian: 27.24秒(原始版本,使用
zip
)
编辑: 添加了Brian使用 izip
修改后的成绩,结果大幅领先!
John的解决方案也很快,尽管它使用了索引(我看到这一点时真的很惊讶!),而Robert和Brian(使用 zip
)的速度比提问者最初的解决方案要慢。
所以我们来展示一下Brian获胜的函数,因为在这个讨论中没有以合适的形式展示:
from itertools import izip
for a_row,b_row in izip(alist, blist):
for a_item, b_item in izip(a_row,b_row):
if a_item.isWhatever:
b_item.doSomething()
我会先写一个生成器方法:
def grid_objects(alist, blist):
for i in range(len(alist)):
for j in range(len(alist[i])):
yield(alist[i][j], blist[i][j])
然后每当你需要遍历这些列表时,你的代码就像这样:
for (a, b) in grid_objects(alist, blist):
if a.is_whatever():
b.do_something()