2008-10-09 110 views
22

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

for i in range(len(alist)): 
    for j in range(len(alist[i])): 
     if alist[i][j].isWhatever: 
      blist[i][j].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() 
+0

这不是一回事,在第二个范围内取了alist [i]的len,为什么你删除了那个索引? – 2008-10-09 21:08:09

+0

我绝对喜欢这个。这可能不是最好的答案,但我很可能会使用它。 – 2008-10-09 21:57:33

+0

这只是Eugene写的东西。如果电网足够大,恐怕这台发电机可能会很慢。毕竟,每个收益率仍然需要四个索引查找。 – DzinX 2008-10-09 22:14:10

-4
for d1 in alist 
    for d2 in d1 
     if d2 = "whatever" 
      do_my_thing() 
+0

你错过了blist – 2008-10-09 20:53:57

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通常是假)。你可以使用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() 

这里使用的是1000×1000阵列,与isWhatever是真实的各种比例我10个循环时序为:

  (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 
+1

这是这里最快的解决方案,但前提是你要将zip更改为itertools.izip(请参阅下面的我的帖子)。 – DzinX 2008-10-09 23:27:44

2

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

3

Generator expressionsizipitertools module会做这里很好:

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而是使用更高效的发电机。
3

由于轻微的风格变化,你可以使用枚举:

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

我不认为你会得到什么显著简单,除非你重新安排你的数据结构费德里科建议。这样你可以把最后一行变成类似“aval.b.doSomething()”的东西。

1

如果你的游戏的一生中两个二维表保持不变,你不能享受Python的多重继承加入ALIST [i] [j]和blist [i] [j]对象类(正如其他人所建议的),你可以在每个一个项指针添加到相应的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__,或者上面的初始化代码可以与您自己的initializat合并离子码e.t.c.之后,您的循环将变为:

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

这应该更有效率。

32

如果有人有兴趣在上述解决方案的性能,在这里,他们是4000x4000电网,从最快到最慢:

编辑:新增布莱恩与izip修改分数,并通过大量的好评!

约翰的解决方案也非常快,虽然它使用的索引(我真的很惊讶,看到这个!),而罗伯特和布赖恩的(与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() 
4

当你用数字电网运行,并希望真正好的表现,你应该考虑使用Numpy。令人惊讶的是,使用起来非常简单,并且可以让您根据网格运行而不是网格上的循环进行思考。性能来自这样的事实,即操作随后通过优化的SSE代码在整个网格上运行。

例如下面是一些使用我编写的代码,可以对由弹簧连接的带电粒子进行蛮力数值模拟的代码。该代码计算具有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 
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() 

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