Итерировать итератор кусками (из n) в Python?
Можете ли вы придумать хороший способ (возможно, с itertools) разделить итератор на куски заданного размера?
Поэтому l=[1,2,3,4,5,6,7]
с chunks(l,3)
становится итератором [1,2,3], [4,5,6], [7]
Я могу придумать небольшую программу, чтобы сделать это, но не очень хорошо с помощью itertools.
Ответы
Ответ 1
Рецепт grouper()
из документации itertools
recipes близок к тому, что вы хотите:
def grouper(n, iterable, fillvalue=None):
"grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
args = [iter(iterable)] * n
return izip_longest(fillvalue=fillvalue, *args)
Он заполнит последний кусок значением заполнения, однако.
Менее общее решение, которое работает только с последовательностями, но обрабатывает последний фрагмент по желанию,
[my_list[i:i + chunk_size] for i in range(0, len(my_list), chunk_size)]
Наконец, решение, которое работает на общих итераторах, ведет себя по желанию,
def grouper(n, iterable):
it = iter(iterable)
while True:
chunk = tuple(itertools.islice(it, n))
if not chunk:
return
yield chunk
Ответ 2
Хотя OP запрашивает функцию, чтобы возвращать куски в виде списка или кортежа, в случае, если вам нужно вернуть итераторы, тогда можно решение Sven Marnach:
def grouper_it(n, iterable):
it = iter(iterable)
while True:
chunk_it = itertools.islice(it, n)
try:
first_el = next(chunk_it)
except StopIteration:
return
yield itertools.chain((first_el,), chunk_it)
Некоторые ориентиры: http://pastebin.com/YkKFvm8b
Он будет немного более эффективным, только если ваша функция выполняет итерацию через элементы в каждом фрагменте.
Ответ 3
Это будет работать с любым итерабельным. Он возвращает генератор генераторов (для полной гибкости). Теперь я понимаю, что это в основном то же самое, что и решение @reclosedevs, но без пуха. Нет необходимости в try...except
по мере распространения StopIteration
, чего мы хотим.
Вызов iterable.next()
необходим, чтобы поднять StopIteration
, когда итерабельность пуста, так как islice
будет продолжать порождать пустые генераторы навсегда, если вы позволите.
Это лучше, потому что это всего лишь две строки, но их легко понять.
def grouper(iterable, n):
while True:
yield itertools.chain([iterable.next()], itertools.islice(iterable, n-1))
Обратите внимание, что iterable.next()
помещается в список. Если iterable.next()
можно повторить и не помещать в список, то itertools.chain
сгладит этот объект. Спасибо Джереми Брауну за то, что он указал на эту проблему.
Ответ 4
Я работал над чем-то сегодня и придумал то, что я считаю простым решением. Он похож на ответ jsbueno, но я считаю, что он даст пустой group
, когда длина iterable
делится на n
. Мой ответ делает простую проверку, когда iterable
исчерпан.
def chunk(iterable, chunk_size):
"""Generate sequences of `chunk_size` elements from `iterable`."""
iterable = iter(iterable)
while True:
chunk = []
try:
for _ in range(chunk_size):
chunk.append(iterable.next())
yield chunk
except StopIteration:
if chunk:
yield chunk
break
Ответ 5
Здесь один, который возвращает ленивые куски; используйте map(list, chunks(...))
, если вы хотите списки.
from itertools import islice, chain
from collections import deque
def chunks(items, n):
items = iter(items)
for first in items:
chunk = chain((first,), islice(items, n-1))
yield chunk
deque(chunk, 0)
if __name__ == "__main__":
for chunk in map(list, chunks(range(10), 3)):
print chunk
for i, chunk in enumerate(chunks(range(10), 3)):
if i % 2 == 1:
print "chunk #%d: %s" % (i, list(chunk))
else:
print "skipping #%d" % i
Ответ 6
"Упрощение лучше, чем сложное" -
простой генератор длиной несколько строк может выполнить эту работу. Просто поместите его в некоторый модуль утилит или так:
def grouper (iterable, n):
iterable = iter(iterable)
count = 0
group = []
while True:
try:
group.append(next(iterable))
count += 1
if count % n == 0:
yield group
group = []
except StopIteration:
yield group
break
Ответ 7
Краткая реализация:
chunker = lambda iterable, n: (ifilterfalse(lambda x: x == (), chunk) for chunk in (izip_longest(*[iter(iterable)]*n, fillvalue=())))
Это работает, потому что [iter(iterable)]*n
- это список, содержащий один и тот же итератор n раз; zipping over, который берет один элемент из каждого итератора в списке, который является тем же самым итератором, в результате каждый zip-элемент содержит группу элементов n
.
izip_longest
необходим, чтобы полностью использовать базовый итерируемый, а не итерацию, останавливающуюся при достижении первого исчерпанного итератора, который отрубает остаток от iterable
. Это приводит к необходимости отфильтровать значение заполнения. Таким образом, немного более надежная реализация:
def chunker(iterable, n):
class Filler(object): pass
return (ifilterfalse(lambda x: x is Filler, chunk) for chunk in (izip_longest(*[iter(iterable)]*n, fillvalue=Filler)))
Это гарантирует, что значение заливки никогда не является элементом в исходном итерабельном. Используя вышеприведенное определение:
iterable = range(1,11)
map(tuple,chunker(iterable, 3))
[(1, 2, 3), (4, 5, 6), (7, 8, 9), (10,)]
map(tuple,chunker(iterable, 2))
[(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)]
map(tuple,chunker(iterable, 4))
[(1, 2, 3, 4), (5, 6, 7, 8), (9, 10)]
Эта реализация почти делает то, что вы хотите, но она имеет проблемы:
def chunks(it, step):
start = 0
while True:
end = start+step
yield islice(it, start, end)
start = end
(Разница заключается в том, что, поскольку islice
не вызывает StopIteration или что-либо еще при вызовах, выходящих за пределы it
, это будет навсегда, а также немного сложная проблема, что результаты islice
должны быть потребляется до повторения этого генератора).
Функциональное создание движущегося окна:
izip(count(0, step), count(step, step))
Итак, это будет:
(it[start:end] for (start,end) in izip(count(0, step), count(step, step)))
Но это все равно создает бесконечный итератор. Итак, вам нужно takewhile (или, возможно, что-то еще может быть лучше), чтобы ограничить его:
chunk = lambda it, step: takewhile((lambda x: len(x) > 0), (it[start:end] for (start,end) in izip(count(0, step), count(step, step))))
g = chunk(range(1,11), 3)
tuple(g)
([1, 2, 3], [4, 5, 6], [7, 8, 9], [10])
Ответ 8
Я забыл, где я нашел вдохновение для этого. Я немного изменил его для работы с MSI GUID в реестре Windows:
def nslice(s, n, truncate=False, reverse=False):
"""Splits s into n-sized chunks, optionally reversing the chunks."""
assert n > 0
while len(s) >= n:
if reverse: yield s[:n][::-1]
else: yield s[:n]
s = s[n:]
if len(s) and not truncate:
yield s
reverse
не относится к вашему вопросу, но это то, что я широко использую с этой функцией.
>>> [i for i in nslice([1,2,3,4,5,6,7], 3)]
[[1, 2, 3], [4, 5, 6], [7]]
>>> [i for i in nslice([1,2,3,4,5,6,7], 3, truncate=True)]
[[1, 2, 3], [4, 5, 6]]
>>> [i for i in nslice([1,2,3,4,5,6,7], 3, truncate=True, reverse=True)]
[[3, 2, 1], [6, 5, 4]]
Ответ 9
Здесь вы идете.
def chunksiter(l, chunks):
i,j,n = 0,0,0
rl = []
while n < len(l)/chunks:
rl.append(l[i:j+chunks])
i+=chunks
j+=j+chunks
n+=1
return iter(rl)
def chunksiter2(l, chunks):
i,j,n = 0,0,0
while n < len(l)/chunks:
yield l[i:j+chunks]
i+=chunks
j+=j+chunks
n+=1
Примеры:
for l in chunksiter([1,2,3,4,5,6,7,8],3):
print(l)
[1, 2, 3]
[4, 5, 6]
[7, 8]
for l in chunksiter2([1,2,3,4,5,6,7,8],3):
print(l)
[1, 2, 3]
[4, 5, 6]
[7, 8]
for l in chunksiter2([1,2,3,4,5,6,7,8],5):
print(l)
[1, 2, 3, 4, 5]
[6, 7, 8]