Итерировать итератор кусками (из n) в Python? [дубликат]
этот вопрос уже есть ответ здесь:
можете ли вы придумать хороший способ (возможно, с помощью itertools) разделить итератор на куски заданного размера?
l=[1,2,3,4,5,6,7]
с chunks(l,3)
становится итератор [1,2,3], [4,5,6], [7]
Я могу придумать небольшую программу, чтобы сделать это, но не очень хороший способ с возможно itertools.
9 ответов:
The
grouper()
рецепт itertools документации!--6-->рецепты приближается к тому, что вы хотите: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
хотя OP просит функцию возвращать куски в виде списка или кортежа, если вам нужно вернуть итераторы, то Свен Марнах решение может быть изменен:
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
это будет немного эффективнее, только если ваша функция перебирает элементы в каждом куске.
это будет работать на любой итерируемый. Он возвращает генератор генераторов (для полной гибкости). Теперь я понимаю, что это в основном то же самое, что и решение @reclosedevs, но без пуха. Нет необходимости
try...except
какStopIteration
распространяется вверх, что мы и хотим.The
next(iterable)
вызов необходим, чтобы поднятьStopIteration
когда iterable пуст, так какislice
будет продолжать порождать пустые генераторы навсегда, если вы позволите ему.это лучше, потому что это только два строки длинные, но легко понять.
def grouper(iterable, n): while True: yield itertools.chain((next(iterable),), itertools.islice(iterable, n-1))
отметим, что
next(iterable)
- это поместить в кортеж. В противном случае, если были Iterable, тогдаitertools.chain
сгладил бы его. Спасибо Джереми Брауну за указание на эту проблему.
я работал над чем-то сегодня и придумал то, что я думаю, это простое решение. Это похоже на jsbueno это ответь, но я считаю его пустым
group
s, когда длина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
вот тот, который возвращает ленивые куски; используйте
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
краткая реализация:
chunker = lambda iterable, n: (ifilterfalse(lambda x: x == (), chunk) for chunk in (izip_longest(*[iter(iterable)]*n, fillvalue=())))
это работает, потому что
[iter(iterable)]*n
- это список, содержащий один и тот же итератор n раз; проскальзывание, которое берет один элемент из каждого итератора в списке,который является тем же итератором, в результате чего каждый 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])
" проще лучше, чем сложный" - простой генератор длиной в несколько строк может выполнить эту работу. Просто поместите его в какой-нибудь модуль утилиты или так:
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
Я забыл, где я нашел вдохновение для этого. Я немного изменил его для работы с 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]]
вот так.
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]