Например, если я делаю простую игру на основе сетки, у меня может быть несколько 2-мерных списков. Один может быть для ландшафта, другой - для объектов и т. д. К сожалению, когда мне нужно перебирать списки и чтобы содержимое квадрата в одном списке влияло на часть другого списка, я должен сделать что-то вроде этого.
for i in range(len(alist)):
for j in range(len(alist[i])):
if alist[i][j].isWhatever:
blist[i][j].doSomething()
Есть ли лучший способ сделать что-то подобное?






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
Вы уверены, что объекты в двух матрицах, которые вы повторяете параллельно, являются экземплярами концептуально различных классов? Как насчет слияния двух классов, в результате чего получается матрица объектов, содержащих и то и другое 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], почему вы удалили этот индекс?
Мне определенно нравится этот больше всего. Возможно, это не лучший ответ, но я, скорее всего, им воспользуюсь.
Это просто синтаксический сахар по сравнению с тем, что написал Юджин. Боюсь, что этот генератор может работать очень медленно, если сетки достаточно большие. В конце концов, каждый выход по-прежнему требует четырех поисков по индексу.
Кроме того, было бы разумно изменить вызовы range () на xrange ().
Здесь очень хорошо подойдут Генератор выражений и 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.Я так считаю, да. Удаляя индексы, вы более четко понимаете, что пытаетесь здесь выполнить (манипулирование объектами, а не их позициями).
Я думаю, что проще перебирать объекты, чем числа. Хороший подход.
В качестве небольшого изменения стиля вы можете использовать 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()
Это должно быть более эффективным.
Это определенно неожиданный ответ. Довольно интересно.
Если кого-то интересует производительность вышеперечисленных решений, вот они для сеток 4000x4000, от самых быстрых до самых медленных:
izip вместо zip)zip)РЕДАКТИРОВАТЬ: Добавлены оценки Брайана с модификацией 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? Кроме того, не могли бы вы проверить и мое предложение?
Добавил, и результаты снова удивляют. В вашем случае кажется, что добавление неожиданной (не в слоты) переменной к объекту происходит очень медленно!
О, инициализацию можно объединить с инициализацией существующей матрицы. Я не знаю, почему вы упомянули «не в слоты»; мы не знаем, как реализованы объекты ячеек. Кстати, вы забыли вызвать a_item.isWhatever (т.е. без скобок)
Ну, я использовал фиктивные объекты с "нормальным" (то есть словарным) управлением атрибутами. Что касается скобок, то в исходном вопросе их нет :)
Это интересно. Я думал, что оригинал будет работать лучше в тех случаях, когда isWhatever встречается редко, поскольку ему вообще не нужен был доступ к блисту, но похоже, что поиск по индексу гораздо важнее. Даже при 0,5% это как бы то ни было, метод izip показал себя лучше.
Я также добавил некоторые тайминги для метода трехмерного списка, и он снова кажется быстрее.
Я думал не о скорости, а о ясности. Еще одна победа для python :-)
Когда вы работаете с числовой сеткой и хотите действительно хорошей производительности, вам следует рассмотреть возможность использования 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()
Если со временем что-то изменится, вам потребуется постоянно обновляйте индекс. Вот почему я использовал набор, поэтому элементы можно добавлять и удалять быстро.
Это самое быстрое решение здесь, но только если вы измените zip на itertools.izip (см. Мой пост где-то ниже).