Как я могу в Python аккуратно перебирать сразу несколько двухмерных списков?

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

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

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

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
23
0
28 120
10
Перейти к ответу Данный вопрос помечен как решенный

Ответы 10

for d1 in alist
   for d2 in d1
      if d2 = "whatever"
          do_my_thing()

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

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

В качестве альтернативы рассмотрите возможность использования трехмерного списка, чтобы ландшафт для ячеек i, j находился в l [i] [j] [0], объекты в l [i] [j] [1] и т. д., Или даже объедините объекты, чтобы вы может сделать [i] [j] .terrain, [i] [j] .object и т. д.

[Править] Тайминги 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 с различными пропорциями isWhat, которые истинны:

            ( 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

Это самое быстрое решение здесь, но только если вы измените zip на itertools.izip (см. Мой пост где-то ниже).

DzinX 10.10.2008 03:27

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

Ответ принят как подходящий

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

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

Это не одно и то же, во втором для диапазона взяли len из alist [i], почему вы удалили этот индекс?

Andrea Ambu 10.10.2008 01:08

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

Eugene M 10.10.2008 01:57

Это просто синтаксический сахар по сравнению с тем, что написал Юджин. Боюсь, что этот генератор может работать очень медленно, если сетки достаточно большие. В конце концов, каждый выход по-прежнему требует четырех поисков по индексу.

DzinX 10.10.2008 02:14

Кроме того, было бы разумно изменить вызовы range () на xrange ().

tzot 10.10.2008 03:05

Здесь очень хорошо подойдут Генератор выражений и 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.

Я так считаю, да. Удаляя индексы, вы более четко понимаете, что пытаетесь здесь выполнить (манипулирование объектами, а не их позициями).

DzinX 10.10.2008 01:30

Я думаю, что проще перебирать объекты, чем числа. Хороший подход.

Brian 10.10.2008 09:29

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

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

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

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

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__, или приведенный выше код инициализации может быть объединен с вашим собственным кодом инициализации и т. д. После этого ваш цикл станет:

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

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

Это определенно неожиданный ответ. Довольно интересно.

Eugene M 10.10.2008 01:55

Если кого-то интересует производительность вышеперечисленных решений, вот они для сеток 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()

Вы пробовали решение Брайана как есть или с itertools.izip? Кроме того, не могли бы вы проверить и мое предложение?

tzot 10.10.2008 02:53

Добавил, и результаты снова удивляют. В вашем случае кажется, что добавление неожиданной (не в слоты) переменной к объекту происходит очень медленно!

DzinX 10.10.2008 03:25

О, инициализацию можно объединить с инициализацией существующей матрицы. Я не знаю, почему вы упомянули «не в слоты»; мы не знаем, как реализованы объекты ячеек. Кстати, вы забыли вызвать a_item.isWhatever (т.е. без скобок)

tzot 10.10.2008 07:03

Ну, я использовал фиктивные объекты с "нормальным" (то есть словарным) управлением атрибутами. Что касается скобок, то в исходном вопросе их нет :)

DzinX 10.10.2008 12:34

Это интересно. Я думал, что оригинал будет работать лучше в тех случаях, когда isWhatever встречается редко, поскольку ему вообще не нужен был доступ к блисту, но похоже, что поиск по индексу гораздо важнее. Даже при 0,5% это как бы то ни было, метод izip показал себя лучше.

Brian 10.10.2008 12:55

Я также добавил некоторые тайминги для метода трехмерного списка, и он снова кажется быстрее.

Brian 10.10.2008 13:17

Я думал не о скорости, а о ясности. Еще одна победа для python :-)

John Fouhy 13.10.2008 01:51

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

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

Если 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()

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

Другие вопросы по теме