用Python中的块(n)迭代一个迭代器?

你可以想一个很好的方法(也许与itertools)将一个迭代器分成给定大小的块?

因此,具有chunks(l,3) l=[1,2,3,4,5,6,7]变成迭代器[1,2,3], [4,5,6], [7]

我可以考虑一个小程序来做到这一点,但可能itertools不是一个好方法。

itertools文档的食谱中的grouper()食谱接近你想要的:

 def grouper(n, iterable, fillvalue=None): "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx" args = [iter(iterable)] * n return izip_longest(fillvalue=fillvalue, *args) 

尽pipe如此,它将填充最后一个填充值。

一个不太常用的解决scheme,只对序列有效,但是按照需要处理最后的块

 [my_list[i:i + chunk_size] for i in range(0, len(my_list), chunk_size)] 

最后,在一般迭代器上工作的解决scheme的行为是所需的

 def grouper(n, iterable): it = iter(iterable) while True: chunk = tuple(itertools.islice(it, n)) if not chunk: return yield chunk 

虽然OP要求函数将块作为列表或元组返回,但如果需要返回迭代器,则可以修改Sven Marnach的解决scheme:

 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解决scheme是一样的,但是没有绒毛。 没有必要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会将该对象展平。 感谢杰里米布朗指出这个问题。

我今天正在做一些事情,并提出我认为是一个简单的解决scheme。 这与jsbueno的答案类似,但是当iterable的长度可以被n整除时,我相信他会产生空的group s。 我的答案做了一个简单的检查,当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(...))使用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或其他任何超出结尾的调用,这将永远产生;还有一个稍微棘手的问题,即迭代之前islice结果必须被消耗)。

要在function上生成移动窗口:

 izip(count(0, step), count(step, step)) 

所以这成为:

 (it[start:end] for (start,end) in izip(count(0, step), count(step, step))) 

但是,这仍然会创build一个无限的迭代器。 所以,你需要花时间(或者别的什么可能会更好)来限制它:

 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 

我忘了我在哪里find了这个灵感。 我已经修改了一下,以便在Windowsregistry中使用MSI GUID:

 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不适用于你的问题,但这是我广泛使用这个function。

 >>> [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]