Как я могу, в python, перебирать несколько списков 2d сразу, чисто?
Если я делаю простую сетевую игру, например, у меня может быть несколько 2d-списков. Может быть, для ландшафта, другой может быть для объектов и т.д. К сожалению, когда мне нужно перебирать списки и содержать содержимое квадрата в одном списке, влияют на часть другого списка, я должен сделать что-то вроде этого.
for i in range(len(alist)):
for j in range(len(alist[i])):
if alist[i][j].isWhatever:
blist[i][j].doSomething()
Есть ли лучший способ сделать что-то вроде этого?
Ответы
Ответ 1
Я бы начал с написания метода генератора:
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
Если кто-то заинтересован в выполнении вышеуказанных решений, здесь они предназначены для сеток 4000x4000, от самых быстрых до самых медленных:
EDIT: добавлены оценки Брайана с модификацией izip
, и он выиграл большую сумму!
Решение John также очень быстро, хотя оно использует индексы (я был очень удивлен, увидев это!), тогда как Роберт и Брайан (с zip
) медленнее, чем исходное решение создателя вопроса.
Итак, пусть присутствует функция Брайан, так как она не показана в соответствующей форме нигде в этом потоке:
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()
Ответ 3
Вы можете закрепить их. то есть:
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()
Однако накладные расходы на zipping и итерацию по элементам могут быть выше, чем ваш оригинальный метод, если вы редко используете 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, [i] [j].object и т.д.
[Edit] DzinX timings фактически показывают, что влияние дополнительной проверки на b_item на самом деле не существенно, рядом с оценкой производительности при повторном просмотре index, поэтому выше (используя 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()
Вот мои тайминги для 10 циклов с использованием массива 1000x1000 с различными пропорциями 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
Ответ 4
Когда вы работаете с сетками чисел и хотите действительно хорошую производительность, вам следует рассмотреть возможность использования Numpy. Он на удивление прост в использовании и позволяет вам думать об операциях с сетками вместо циклов над сетками. Производительность исходит из того, что операции затем выполняются над целыми сетками с оптимизированным кодом SSE.
Например, вот несколько numpy с использованием кода, который я написал, который делает грубое численное моделирование заряженных частиц, связанных пружинами. Этот код вычисляет временную привязку для 3d-системы с 100 узлами и 99 ребрами в 31 мс. Это более чем в 10 раз быстрее, чем лучший чистый код python, который я мог бы придумать.
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 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
Ответ 5
выражения генератора и izip
из модуль itertools будет очень хорошо здесь:
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
выше означает:
- возьмите каждую строку из объединенных сеток
alist
и blist
и сделайте из них кортеж (aline, bline)
- теперь объединить эти списки с
izip
снова и извлечь из них каждый элемент (pair
).
Этот метод имеет два преимущества:
- нет индексов, используемых в любом месте
- вам не нужно создавать списки с помощью
zip
и вместо этого использовать более эффективные генераторы с izip
.
Ответ 6
Как небольшое изменение стиля, вы можете использовать перечисление:
for i, arow in enumerate(alist):
for j, aval in enumerate(arow):
if aval.isWhatever():
blist[i][j].doSomething()
Я не думаю, что вы получите что-то значительно проще, если вы не измените свои структуры данных, как предлагает Федерико. Чтобы вы могли превратить последнюю строку в нечто вроде "aval.b.doSomething()".
Ответ 7
Вы уверены, что объекты в двух матрицах, которые вы повторяете параллельно, являются экземплярами концептуально различных классов? Как насчет слияния двух классов, заканчивающихся матрицей объектов, содержащих как isWhatever(), так и doSomething()?
Ответ 8
Если два 2D-списка остаются постоянными во время жизни вашей игры, и вы не можете наслаждаться множественным наследованием 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__
, или код инициализации выше может быть объединен с вашим собственным кодом инициализации e.t.c. После этого ваш цикл станет следующим:
for a_row in alist:
for a_item in a_row:
if a_item.isWhatever():
a_item.b_item.doSomething()
Это должно быть более эффективным.
Ответ 9
Если 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()
Если изменения со временем меняются, вам нужно будет
обновите индекс. Вот почему я использовал
набор, поэтому элементы могут быть добавлены и удалены быстро.
Ответ 10
for d1 in alist
for d2 in d1
if d2 = "whatever"
do_my_thing()