Подтвердить что ты не робот

Как я могу, в 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()

Есть ли лучший способ сделать что-то вроде этого?

4b9b3361

Ответ 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()