If I'm making a simple grid based game, for example, I might have a few 2d lists. One might be for terrain, another might be for objects, etc. Unfortunately, when I need to iterate over the lists and have the contents of a square in one list affect part of another list, I have to do something like this.
for i in range(len(alist)):
for j in range(len(alist[i])):
if alist[i][j].isWhatever:
blist[i][j].doSomething()
Is there a nicer way to do something like this?
Generator expressions and
izip
from itertools module will do very nicely here:The line in
for
statement above means:alist
andblist
and make a tuple from them(aline, bline)
izip
again and take each element from them (pair
).This method has two advantages:
zip
and use more efficient generators withizip
instead.If anyone is interested in performance of the above solutions, here they are for 4000x4000 grids, from fastest to slowest:
izip
instead ofzip
)zip
)EDIT: Added Brian's scores with
izip
modification and it won by a large amount!John's solution is also very fast, although it uses indices (I was really surprised to see this!), whereas Robert's and Brian's (with
zip
) are slower than the question creator's initial solution.So let's present Brian's winning function, as it is not shown in proper form anywhere in this thread:
When you are operating with grids of numbers and want really good performance, you should consider using Numpy. It's surprisingly easy to use and lets you think in terms of operations with grids instead of loops over grids. The performance comes from the fact that the operations are then run over whole grids with optimised SSE code.
For example here is some numpy using code that I wrote that does brute force numerical simulation of charged particles connected by springs. This code calculates a timestep for a 3d system with 100 nodes and 99 edges in 31ms. That is over 10x faster than the best pure python code I could come up with.
If
a.isWhatever
is rarely true you could build an "index" once:and each time you want something to be done:
If a changes over time, then you will need to keep the index up-to-date. That's why I used a set, so items can be added and removed fast.
As a slight style change, you could use enumerate:
I don't think you'll get anything significantly simpler unless you rearrange your data structures as Federico suggests. So that you could turn the last line into something like "aval.b.doSomething()".
If the two 2D-lists remain constant during the lifetime of your game and you can't enjoy Python's multiple inheritance to join the alist[i][j] and blist[i][j] object classes (as others have suggested), you could add a pointer to the corresponding b item in each a item after the lists are created, like this:
Various optimisations can apply here, like your classes having
__slots__
defined, or the initialization code above could be merged with your own initialization code e.t.c. After that, your loop will become:That should be more efficient.