Создание понимания списка Python с помощью if и break с вложенными циклами for

Из этого ответа я заметил, что код

for i in userInput:
    if i in wordsTask:
        a = i
        break

может быть записано как понимание списка следующим образом:

next([i for i in userInput if i in wordsTask])

У меня есть аналогичная проблема, заключающаяся в том, что я хотел бы написать следующий (упрощенный из исходной проблемы) код с точки зрения понимания списка:

 for i in xrange(N):
     point = Point(long_list[i],lat_list[i])
     for feature in feature_list:
         polygon = shape(feature['geometry'])
         if polygon.contains(point):
             new_list.append(feature['properties'])
             break

Я ожидаю, что каждый point будет связан с одним полигоном из списка объектов. Следовательно, как только многоугольник, содержащий точку, найден, break используется для перехода к следующей точке. Следовательно, new_list будет иметь ровно N элементов.

Я написал это как понимание списка следующим образом:

new_list = [feature['properties'] for i in xrange(1000) for feature in feature_list if shape(feature['geometry']).contains(Point(long_list[i],lat_list[i])]

Конечно, при этом не учитывается break в выражении if, и поэтому это занимает значительно больше времени, чем использование вложенных циклов for. Используя совет из сообщения, указанного выше (которое я, вероятно, не совсем понимаю), я сделал

new_list2 = next(feature['properties'] for i in xrange(1000) for feature in feature_list if shape(feature['geometry']).contains(Point(long_list[i],lat_list[i]))

Однако new_list2 имеет гораздо меньше N элементов (в моем случае N=1000 и new_list2 было всего 5 элементов)

Вопрос 1: Стоит ли вообще делать это как понимание списка? Единственная причина в том, что я читал, что понимание списков обычно немного быстрее, чем вложенные циклы for. С 2 миллионами точек данных каждая секунда на счету.

Вопрос 2: Если да, то как мне включить оператор break в понимание списка?

Вопрос 3: Какая ошибка возникла при использовании next так, как я это делал?

Большое спасибо за ваше время и любезную помощь.


person mwolverine    schedule 08.07.2016    source источник
comment
next() используется, чтобы взять первый элемент списка и эффективно отбросить остальную часть списка. В идеале Python создает только те элементы, которые вы запрашиваете, и если вы никогда не пытаетесь просмотреть остальную часть списка, он никогда не вычисляется. А как насчет new_list3 = [next(feature['properties'] for feature in feature_list if shape(feature['geometry']).contains(Point(long_list[i], lat_list[i]))) for i in xrange(1000)] ?   -  person TessellatingHeckler    schedule 08.07.2016


Ответы (2)


Понимание списков не обязательно быстрее, чем цикл for. Если у вас есть шаблон вроде:

some_var = []
for ...:
    if ...:
        some_var.append(some_other_var)

тогда да, понимание списка быстрее, чем связка .append()s. Однако у вас есть смягчающие обстоятельства. Во-первых, это фактически генераторное выражение в случае next(...), потому что вокруг него нет [ и ].

  • На самом деле вы не создаете список (и, следовательно, не используете .append()). Вы просто получаете одно значение.
  • Ваш генератор вызывает Point(long_list[i], lat_list[i]) один раз для каждой функции для каждого i в xrange(N), тогда как цикл вызывает его только один раз для каждого i.
  • и, конечно же, ваше генераторное выражение не работает.

Почему ваше выражение генератора не работает? Потому что он находит только первое значение в целом. С другой стороны, цикл находит первое значение для каждого i. Вы видите разницу? Генераторное выражение выходит из обоих циклов, а цикл for выходит только из внутреннего.


Если вы хотите немного улучшить производительность, используйте itertools.izip() (или просто zip() в Python 3):

from itertools import izip

for long, lat in izip(long_list, lat_list):
    point = Point(long, lat)
    ...
person zondo    schedule 08.07.2016
comment
Большое спасибо за ваш ответ. Теперь я понимаю, что next вызывает разрыв обоих циклов, поэтому он не работает. Я также понимаю, как izip может повысить производительность. Однако, если вы посмотрите на мой исходный код, я делаю именно .append() (new_list.append(feature['properties']); break). Ваше первоначальное заявление о смягчающих обстоятельствах остается в силе? - person mwolverine; 08.07.2016

Я не знаю, что сложное понимание списка или выражения генератора намного быстрее, чем вложенные циклы, если они работают с одним и тем же алгоритмом (например, посещают одинаковое количество значений). Чтобы получить окончательный ответ, вам, вероятно, следует попробовать реализовать решение в обоих направлениях и проверить, что быстрее для ваших реальных данных.

Что касается того, как сократить внутренний цикл, но не внешний, вам нужно будет поместить вызов next внутри понимания основного списка с отдельным выражением генератора внутри него:

new_list = [next(feature['properties'] for feature in feature_list
                                       if shape(feature['shape']).contains(Point(long, lat)))
            for long, lat in zip(long_list, lat_list)]

Я изменил еще одну вещь: вместо того, чтобы индексировать long_list и lat_list с индексами из range, я использую zip для их параллельного перебора.

Обратите внимание, что если создание объектов Point снова и снова занимает слишком много времени, вы можете упростить эту часть кода, добавив еще одно выражение вложенного генератора, которое создает точки и позволяет привязать их к (повторно используемому) имени:

new_list = [next(feature['properties'] for feature in feature_list
                                       if shape(feature['shape']).contains(point))
            for point in (Point(long, lat) for long, lat in zip(long_list, lat_list))]
person Blckknght    schedule 08.07.2016