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


Answers

虽然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 : http://pastebin.com/YkKFvm8b

只有当你的函数遍历每个块中的元素时,它才会更有效率。

Question

这个问题在这里已经有了答案:

你能想出一个很好的方法(也许用itertools)将一个迭代器分成给定大小的块吗?

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

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




我今天正在做一些事情,并提出我认为是一个简单的解决方案。 它与jsbueno's答案类似,但我相信当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



一个简洁的实现是:

chunker = lambda iterable, n: (ifilterfalse(lambda x: x == (), chunk) for chunk in (izip_longest(*[iter(iterable)]*n, fillvalue=())))

这是[iter(iterable)]*n因为[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)))

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

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



我忘了我在哪里找到了灵感。 我对它进行了一些修改,以便在Windows注册表中使用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不适用于你的问题,但这是我广泛使用这个功能。

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





Related