Оптимизировать понимание списка - PullRequest
0 голосов
/ 15 июля 2010

Вот фрагмент кода, который показывает код, который я хотел бы оптимизировать:

result = [(item, foo(item))
          for item in item_list
          if cond1(item) and cond2(foo(item))]

В приведенном выше фрагменте я вызываю foo(item) дважды. Я не могу придумать способ итерации по списку только после того, как поддержу item и foo(item) для условного и списка результатов.

То есть я хотел бы сохранить item и foo(item) без необходимости повторения списка дважды и без необходимости вызывать foo(item) дважды.

Я знаю, что могу сделать это со вторым пониманием вложенного списка:

result = [(item, foo_item)
          for item, foo_item in [(i, foo(i)) for i in item_list]
          if cond1(item) and cond2(foo_item)]

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

Итак, первый пример вызывает foo дважды для каждого элемента списка. Второй пример проходит по списку дважды (или отображается как). Я хотел бы сделать один цикл и вызвать foo один раз для каждого элемента.

Ответы [ 5 ]

4 голосов
/ 15 июля 2010

Как мне неоднократно говорили здесь, лучше всего в таких случаях вообще не использовать понимание списка:

result = []
for item in item_list:
    if cond1(item):
        value = foo(item)
        if cond2(value):
            result.append((item, value))

Но я упрямый, поэтому давайте посмотрим, что я могу придуматьс (и сохраняйте понимание) (о, подождите - я неправильно понял ваш код. Тем не менее - разворачивание и наличие промежуточных переменных - это прямой способ не повторять вызов)

4 голосов
/ 15 июля 2010

Это не так, но здесь:

result = [(item, foo_item)
    for item, foo_item in ((i, foo(i)) for i in item_list)
    if cond1(item) and cond2(foo_item)]

Превращение понимания внутреннего списка в выражение генератора гарантирует, что мы не используем ненужный временный список.

3 голосов
/ 15 июля 2010

Использовать выражения генератора.

result = [(item, foo_item)
          for item, foo_item in ((i, foo(i)) for i in item_list)
          if cond1(item) and cond2(foo_item)]

Интерпретатор будет проходить через каждый элемент ровно один раз, потому что выражение генератора будет вычислять (i, foo(i)) только тогда, когда этого требует внешний цикл.

Предполагая, что foo стоит дорого и не имеет побочных эффектов, я бы даже попытался сделать это:

result = [(item, foo_item)
          for item, foo_item in ((i, foo(i)) for i in item_list if cond1(i))
          if cond2(foo_item)]

, чтобы foo не вызывался для элементов, которые не проходят первое условие.На самом деле это выглядит лучше для меня, когда написано функционально:

from itertools import imap, ifilter
result = filter((lambda i,f:cond2(f)),
           imap((lambda i:(i, foo(i))),
             ifilter(cond1, item_list)))

... но я могу быть субъективным.

3 голосов
/ 15 июля 2010

Как это выглядит?

result = [ (i, fi) for i  in item_list if cond1(i)
                   for fi in (foo(i),) if cond2(fi) ]
1 голос
/ 18 июля 2010

Это одна из многих причин, по которым у нас есть генераторы:

def generator( items ):
    for item in items:
        if cond1(item):
            food = foo(item)
            if food:
                yield item, food

result = list(generator(item_list))

ЛК хороши только тогда, когда они хорошо выглядят - если вам нужно распределить их по 3 строкам, чтобы сделать их читаемыми, это плохая идея.

...