Если вы работаете с последовательностями, решение @nneonneo будет максимально эффективным. Если вам нужно решение, которое работает с произвольными итерируемыми объектами, вы можете изучить некоторые из itertools. рецепты. например окунь:
def grouper(iterable, n, fillvalue=None):
"Collect data into fixed-length chunks or blocks"
# grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx
args = [iter(iterable)] * n
return itertools.izip_longest(fillvalue=fillvalue, *args)
Я обычно не использую этот, потому что он «заполняет» последнюю группу None
, так что она имеет ту же длину, что и другие. Обычно я определяю свой собственный вариант, который не имеет такого поведения:
def grouper2(iterable, n):
iterable = iter(iterable)
while True:
tup = tuple(itertools.islice(iterable, 0, n))
if tup:
yield tup
else:
break
Это дает кортежи запрошенного размера. Обычно этого достаточно, но для развлечения мы можем написать генератор, который возвращает ленивые итерации правильного размера, если мы действительно хотим...
Я думаю, что «лучшее» решение здесь немного зависит от решаемой проблемы, особенно от размера групп и объектов в исходной итерации и типа исходной итерации. Как правило, эти последние 2 рецепта найдут меньшее применение, потому что они более сложны и редко используются. Однако, если вы чувствуете себя авантюрно и в настроении немного повеселиться, читайте дальше!
Единственная реальная модификация, которая нам нужна, чтобы получить ленивую итерацию вместо кортежа, — это возможность «заглянуть» в следующее значение в islice
, чтобы увидеть, есть ли там что-нибудь. здесь я просто смотрю на значение - если оно отсутствует, будет поднято StopIteration
, что остановит генератор так же, как если бы он закончился нормально. Если он есть, я возвращаю его с помощью itertools.chain
:
def grouper3(iterable, n):
iterable = iter(iterable)
while True:
group = itertools.islice(iterable, n)
item = next(group) # raises StopIteration if the group doesn't yield anything
yield itertools.chain((item,), group)
Однако будьте осторожны, эта последняя функция только "работает", если вы полностью исчерпали каждую полученную итерацию, прежде чем перейти к следующей. В крайнем случае, когда вы не исчерпаете какие-либо итерации, например. list(grouper3(..., n))
вы получите "m" итераций, которые дают только 1 элемент, а не n
(где "m" - это "длина" входной итерации). Иногда такое поведение может быть полезным, но не всегда. Мы также можем исправить это, если воспользуемся рецептом itertools «consume» (который также требует импорта collections
в дополнение к itertools
):
def grouper4(iterable, n):
iterable = iter(iterable)
group = []
while True:
collections.deque(group, maxlen=0) # consume all of the last group
group = itertools.islice(iterable, n)
item = next(group) # raises StopIteration if the group doesn't yield anything
group = itertools.chain((item,), group)
yield group
Конечно, list(grouper4(..., n))
вернет пустые итерации — любое значение, не извлеченное из «группы» до следующего вызова next
(например, когда цикл for
возвращается к началу), никогда не будет возвращено.
person
mgilson
schedule
29.05.2014