我怎么能在python中一次遍历多个2d列表呢?

时间:2008-10-09 20:32:14

标签: python

例如,如果我制作一个简单的基于网格的游戏,我可能会有一些2d列表。一个可能是地形,另一个可能是对象等。不幸的是,当我需要迭代列表并让一个列表中的正方形内容影响另一个列表的一部分时,我必须做这样的事情。

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

有没有更好的方法来做这样的事情?

10 个答案:

答案 0 :(得分:32)

如果有人对上述解决方案的性能感兴趣,那么它们适用于4000x4000网格,从最快到最慢:

编辑:通过izip修改添加了Brian的分数,并获得了大量奖金!

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

答案 1 :(得分: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()

答案 2 :(得分: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,否则它可能会稍慢一些。

或者,考虑使用3D列表,因此单元格i,j的地形位于l [i] [j] [0],l [i] [j] [1]等处的对象,或甚至组合对象所以你可以做[i] [j] .terrain,a [i] [j] .object等。

[编辑] DzinX's timings实际上表明额外检查对b_item的影响并不是很大,旁边是按索引重新查找的性能损失,所以上面(使用izip)似乎最快

我现在也对3d方法进行了快速测试,但它似乎仍然更快,所以如果你能以这种形式存储数据,那么访问它可能更简单,更快捷。以下是使用它的示例:

# 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是真的:

            ( 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

答案 3 :(得分:4)

当您使用数字网格进行操作并希望获得非常好的性能时,您应该考虑使用Numpy。它的使用非常简单,让您可以考虑使用网格而不是网格上的循环来进行操作。性能来自这样一个事实,即操作随后通过优化的SSE代码在整个网格上运行。

例如,这里有一些使用我编写的代码的numpy,它通过弹簧连接的带电粒子进行强力数值模拟。此代码计算具有100个节点的3d系统的时间步长和31ms内的99个边缘。这比我能提出的最好的纯Python代码快了10倍。

from numpy import array, sqrt, float32, newaxis
def evolve(points, velocities, edges, timestep=0.01, charge=0.1, mass=1., edgelen=0.5, dampen=0.95):
    """Evolve a n body system of electrostatically repulsive nodes connected by
       springs by one timestep."""
    velocities *= dampen

    # calculate matrix of distance vectors between all points and their lengths squared
    dists = array([[p2 - p1 for p2 in points] for p1 in points])
    l_2 = (dists*dists).sum(axis=2)

    # make the diagonal 1's to avoid division by zero
    for i in xrange(points.shape[0]):
        l_2[i,i] = 1

    l_2_inv = 1/l_2
    l_3_inv = l_2_inv*sqrt(l_2_inv)

    # repulsive force: distance vectors divided by length cubed, summed and multiplied by scale
    scale = timestep*charge*charge/mass
    velocities -= scale*(l_3_inv[:,:,newaxis].repeat(points.shape[1], axis=2)*dists).sum(axis=1)

    # calculate spring contributions for each point
    for idx, (point, outedges) in enumerate(izip(points, edges)):
        edgevecs = point - points.take(outedges, axis=0)
        edgevec_lens = sqrt((edgevecs*edgevecs).sum(axis=1))
        scale = timestep/mass
        velocities[idx] += (edgevecs*((((edgelen*scale)/edgevec_lens - scale))[:,newaxis].repeat(points.shape[1],axis=1))).sum(axis=0)

    # move points to new positions
    points += velocities*timestep

答案 4 :(得分:3)

来自Generator expressions

itertools moduleizip会在这里做得非常好:

from itertools import izip
for a, b in (pair for (aline, bline) in izip(alist, blist) 
             for pair in izip(aline, bline)):
    if a.isWhatever:
        b.doSomething()

上面for语句中的行表示:

  • 从合并网格alistblist中获取每一行并从中创建一个元组(aline, bline)
  • 现在再次将这些列表与izip合并,并从中获取每个元素(pair)。

这种方法有两个好处:

  • 在任何地方都没有使用指数
  • 您不必使用zip创建列表,而是使用效率更高的生成器代替izip

答案 5 :(得分:3)

作为轻微的样式更改,您可以使用枚举:

for i, arow in enumerate(alist):
    for j, aval in enumerate(arow):
        if aval.isWhatever():
            blist[i][j].doSomething()

除非你按照Federico的建议重新排列数据结构,否则我认为你不会有任何明显的改进。这样你就可以把最后一行变成“aval.b.doSomething()”。

答案 6 :(得分:2)

您确定要并行迭代的两个矩阵中的对象是概念上不同的类的实例吗?如何合并两个类,最后是一个包含 both 的对象矩阵isWhatever()和doSomething()?

答案 7 :(得分:1)

如果两个2D列表在游戏的生命周期内保持不变,则无法使用Python的多重继承加入alist [i] [j]和blist [i] [j ]对象类(正如其他人建议的那样),您可以在创建列表后在每个 a 项中添加指向相应 b 项的指针,如下所示:

for a_row, b_row  in itertools.izip(alist, blist):
    for a_item, b_item in itertools.izip(a_row, b_row):
        a_item.b_item= b_item

此处可以应用各种优化,例如定义了__slots__的类,或者上面的初始化代码可以与您自己的初始化代码e.t.c合并。之后,您的循环将变为:

for a_row in alist:
    for a_item in a_row:
        if a_item.isWhatever():
            a_item.b_item.doSomething()

这应该更有效率。

答案 8 :(得分:0)

如果a.isWhatever很少是真的,你可以建立一次“索引”:

a_index = set((i,j) 
              for i,arow in enumerate(a) 
              for j,a in enumerate(arow) 
              if a.IsWhatever())

每次你想要完成任务:

for (i,j) in a_index:
    b[i][j].doSomething()

如果随时间变化,那么您将需要 使索引保持最新状态。这就是我使用的原因 一套,所以可以快速添加和删除项目。

答案 9 :(得分:-4)

for d1 in alist
   for d2 in d1
      if d2 = "whatever"
          do_my_thing()