Как я могу, в python, перебрать сразу несколько 2d списков, чисто? - PullRequest
22 голосов
/ 10 октября 2008

Например, если я делаю простую сеточную игру, у меня может быть несколько двумерных списков. Один может быть для ландшафта, другой - для объектов и т. Д. К сожалению, когда мне нужно перебрать списки и сделать так, чтобы содержимое квадрата в одном списке влияло на часть другого списка, я должен сделать что-то вроде этого.

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

Есть ли лучший способ сделать что-то подобное?

Ответы [ 10 ]

32 голосов
/ 10 октября 2008

Если кто-то заинтересован в производительности вышеупомянутых решений, то здесь они для сеток 4000x4000, от самых быстрых до самых медленных:

РЕДАКТИРОВАТЬ : Добавлены очки Брайана с модификацией izip, и он выиграл на большую сумму!

Решение Джона также очень быстрое, хотя оно использует индексы (я был очень удивлен, увидев это!), Тогда как решение Роберта и Брайана (с 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()
15 голосов
/ 10 октября 2008

Я бы начал с написания метода генератора:

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()
10 голосов
/ 10 октября 2008

Вы можете застегнуть их на молнию. то есть:

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.isWh независимо от того, что обычно является False). Вы можете использовать itertools.izip вместо zip, чтобы уменьшить влияние этого на память, но, вероятно, все равно будет немного медленнее, если вам не нужен b_item.

В качестве альтернативы, рассмотрите возможность использования списка 3D вместо этого, чтобы местность для ячейки i, j была в l [i] [j] [0], объекты в l [i] [j] [1] и т. Д., Или даже объединить объекты, так что вы можете сделать [i] [j] .terrain, [i] [j] .объект и т. д.

[Редактировать] Времена DzinX фактически показывают, что влияние дополнительной проверки для b_item не является значительным, кроме потери производительности при повторном поиске по индексу, поэтому выше (с использованием izip) Кажется, самый быстрый.

Я также дал быстрый тест для трехмерного подхода, и он все еще кажется более быстрым, поэтому, если вы сможете хранить свои данные в этой форме, доступ к ним может быть как более простым, так и более быстрым. Вот пример его использования:

# 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, с различными пропорциями isWh независимо от того, являются ли они истинными:

            ( 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 голосов
/ 10 октября 2008

Когда вы работаете с сетками чисел и хотите действительно хорошей производительности, вам следует рассмотреть возможность использования Numpy . Он удивительно прост в использовании и позволяет думать с точки зрения операций с сетками, а не с циклами над сетками. Производительность достигается за счет того, что операции затем выполняются по целым сеткам с оптимизированным кодом SSE.

Например, здесь приведен пример с использованием кода, который я написал и который осуществляет численное моделирование заряженных частиц, связанных пружинами. Этот код вычисляет временной шаг для 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'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
3 голосов
/ 10 октября 2008

В качестве небольшого изменения стиля вы можете использовать перечисление:

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

Я не думаю, что вы получите что-то значительно более простое, если не перестроите свои структуры данных, как предлагает Федерико. Чтобы вы могли превратить последнюю строку во что-то вроде «aval.b.doSomething ()».

3 голосов
/ 10 октября 2008

Выражения генератора и 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.
2 голосов
/ 10 октября 2008

Вы уверены, что объекты в двух матрицах, которые вы повторяете параллельно, являются экземплярами концептуально различных классов? Как насчет слияния двух классов, заканчивающихся матрицей объектов, которые содержат и isWhwhat () и doSomething ()?

1 голос
/ 10 октября 2008

Если два 2D-списка остаются постоянными в течение всей жизни вашей игры и , вы не можете наслаждаться множественным наследованием Python для присоединения к списку alist [i] [j] и blist [i] [j] Классы объектов (как предлагали другие), вы можете добавить указатель на соответствующий элемент b в каждом элементе a после создания списков, например:

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

Это должно быть более эффективным.

0 голосов
/ 11 октября 2008

Если a.isWhatever редко имеет значение true, вы можете создать «индекс» один раз:

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

Если изменения со временем, то вам нужно будет поддерживать индекс в актуальном состоянии. Вот почему я использовал набор, поэтому предметы могут быть добавлены и удалены быстро.

0 голосов
/ 10 октября 2008
for d1 in alist
   for d2 in d1
      if d2 = "whatever"
          do_my_thing()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...