一个简洁的实现是:
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 不会在超过it 结束的调用上引发 StopIteration 或其他任何事情,这将永远产生;还有一个稍微棘手的问题,islice 结果必须是在迭代此生成器之前消耗)。
功能性地生成移动窗口:
izip(count(0, step), count(step, step))
这样就变成了:
(it[start:end] for (start,end) in izip(count(0, step), count(step, step)))
但是,这仍然会创建一个无限迭代器。所以,你需要暂时(或者其他可能更好的方法)来限制它:
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])