iterator 可迭代对象 - 用Python中的块(n)迭代一个迭代器?





generator遍历 iterable (9)


“简单比复杂更好” - 几行简单的发电机可以完成这项工作。 只需将其放置在某些实用程序模块中即可:

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

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

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

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

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




虽然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

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




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

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



这是一个返回懒块的东西; 如果你想要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



这将适用于任何迭代。 它返回发电机的发电机(充分的灵活性)。 我现在意识到它基本上和@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会将该对象展平。 感谢杰里米布朗指出这个问题。




一个简洁的实现是:

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



干得好。

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]



我忘了我在哪里找到了灵感。 我对它进行了一些修改,以便在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]]



是。

>>> b = (True if 5 > 4 else False)
>>> print b
True




python iterator