如何在Python中同时优雅地遍历多个二维列表?

23 投票
11 回答
32098 浏览
提问于 2025-04-11 09:24

如果我在制作一个简单的基于网格的游戏,比如说,我可能会有几个二维列表。一个列表可能用来表示地形,另一个列表可能用来表示物体等等。不幸的是,当我需要遍历这些列表,并让一个列表中某个方块的内容影响另一个列表的一部分时,我就得像这样做。

for i in range(len(alist)):
    for j in range(len(alist[i])):
        if alist[i][j].isWhatever:
            blist[i][j].doSomething()

有没有更好的方法来做到这一点呢?

11 个回答

10

你可以把它们压缩在一起。比如:

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
33

如果有人对上面提到的解决方案的性能感兴趣,这里是针对4000x4000的网格,从最快到最慢的排序:

编辑: 添加了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()
15

我会先写一个生成器方法:

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()

撰写回答