Подтвердить что ты не робот

Как ограничить размер понимания?

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

Следующий код не будет выполнен:

a = [1, 2, 1, 2, 1, 2]
b = [i for i in a if i == 1 and len(b) < 3]

с

Traceback (most recent call last):
  File "compr.py", line 2, in <module>
    b = [i for i in a if i == 1 and len(b) < 3]
  File "compr.py", line 2, in <listcomp>
    b = [i for i in a if i == 1 and len(b) < 3]
NameError: name 'b' is not defined

потому что b не определяется еще во время создания понимания.

Есть ли способ ограничить размер нового списка во время сборки?

Примечание. Я могу разбить понимание в цикле for с правильным break, когда счетчик достигнут, но я хотел бы знать, есть ли механизм, который использует понимание.

4b9b3361

Ответ 1

Вы можете использовать выражение генератора для фильтрации, а затем использовать islice() для ограничения числа итераций:

from itertools import islice

filtered = (i for i in a if i == 1)
b = list(islice(filtered, 3))

Это гарантирует, что вы не будете делать больше работы, чем вам нужно, чтобы создать эти 3 элемента.

Обратите внимание, что больше нет смысла использовать понимание списка; понимание списка не может быть нарушено, вы заперты в итерации до конца.

Ответ 2

@Martijn Pieters полностью прав, что itertools.islice - лучший способ решить эту проблему. Однако, если вы не возражаете против дополнительной (внешней) библиотеки, вы можете использовать iteration_utilities, которая обертывает много этих itertools и их приложений (и некоторых дополнительных). Это может сделать это немного проще, по крайней мере, если вам нравится функциональное программирование:

>>> from iteration_utilities import Iterable

>>> Iterable([1, 2, 1, 2, 1, 2]).filter((1).__eq__)[:2].as_list()
[1, 1]

>>> (Iterable([1, 2, 1, 2, 1, 2])
...          .filter((1).__eq__)   # like "if item == 1"
...          [:2]                  # like "islice(iterable, 2)"
...          .as_list())           # like "list(iterable)"
[1, 1]

Класс iteration_utilities.Iterable использует внутренние генераторы, поэтому он обрабатывает столько элементов, сколько необходимо, пока вы не вызовете какой-либо из as_* (или get_*) -методы.


Отказ от ответственности: я являюсь автором библиотеки iteration_utilities.

Ответ 3

Вы можете использовать itertools.count для создания счетчика и itertools.takewhile, чтобы остановить итерацию по генератору, когда счетчик достигнет желаемого целого числа (3 в этом случае):

from itertools import count, takewhile
c = count()
b = list(takewhile(lambda x: next(c) < 3, (i for i in a if i == 1)))

Или аналогичная идея, строящая конструкцию, чтобы поднять StopIteration, чтобы завершить генератор. Это ближе всего к вашей первоначальной идее нарушающей понимание списка, но я бы не рекомендовал ее в качестве лучшей практики:

c = count()
b = list(i if next(c) < 3 else next(iter([])) for i in a if i == 1)

Примеры:

>>> a = [1,2,1,4,1,1,1,1]

>>> c = count()
>>> list(takewhile(lambda x: next(c) < 3, (i for i in a if i == 1)))
[1, 1, 1]

>>> c = count()
>>> list(i if next(c) < 3 else next(iter([])) for i in a if i == 1)
[1, 1, 1]

Ответ 4

использовать перечисление:

b = [n for i,n in enumerate(a) if n==1 and i<3]