在python中每n个项拆分一个生成器/ iterable(splitEvery)

Jam*_*oks 31 python iterator split

我正在尝试用Python编写Haskel函数'splitEvery'.这是它的定义:

splitEvery :: Int -> [e] -> [[e]]
    @'splitEvery' n@ splits a list into length-n pieces.  The last
    piece will be shorter if @n@ does not evenly divide the length of
    the list.
Run Code Online (Sandbox Code Playgroud)

这个的基本版本工作正常,但我想要一个适用于生成器表达式,列表和迭代器的版本.并且,如果有一个发电机作为输入,它应该返回一个发电机作为输出!

测试

# should not enter infinite loop with generators or lists
splitEvery(itertools.count(), 10)
splitEvery(range(1000), 10)

# last piece must be shorter if n does not evenly divide
assert splitEvery(5, range(9)) == [[0, 1, 2, 3, 4], [5, 6, 7, 8]]

# should give same correct results with generators
tmp = itertools.islice(itertools.count(), 10)
assert list(splitEvery(5, tmp)) == [[0, 1, 2, 3, 4], [5, 6, 7, 8]]
Run Code Online (Sandbox Code Playgroud)

目前的实施

这是我目前的代码,但它不适用于简单的列表.

def splitEvery_1(n, iterable):
    res = list(itertools.islice(iterable, n))
    while len(res) != 0:
        yield res
        res = list(itertools.islice(iterable, n))
Run Code Online (Sandbox Code Playgroud)

这个不适用于生成器表达式(感谢jellybean用于修复它):

def splitEvery_2(n, iterable): 
    return [iterable[i:i+n] for i in range(0, len(iterable), n)]
Run Code Online (Sandbox Code Playgroud)

必须有一段简单的代码来完成拆分.我知道我可以有不同的功能,但它似乎应该是容易的事情.我可能会陷入一个不重要的问题,但这真的让我烦恼.


它类似于http://docs.python.org/library/itertools.html#itertools.groupby中的石斑鱼,但我不希望它填充额外的值.

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

它确实提到了截断最后一个值的方法.这不是我想要的.

保证了迭代的从左到右的评估顺序.这使得使用izip(*[iter(s)]*n)将数据序列聚类成n长度组的习惯成为可能.

list(izip(*[iter(range(9))]*5)) == [[0, 1, 2, 3, 4]]
# should be [[0, 1, 2, 3, 4], [5, 6, 7, 8]]
Run Code Online (Sandbox Code Playgroud)

Rob*_*let 47

from itertools import islice

def split_every(n, iterable):
    i = iter(iterable)
    piece = list(islice(i, n))
    while piece:
        yield piece
        piece = list(islice(i, n))
Run Code Online (Sandbox Code Playgroud)

一些测试:

>>> list(split_every(5, range(9)))
[[0, 1, 2, 3, 4], [5, 6, 7, 8]]

>>> list(split_every(3, (x**2 for x in range(20))))
[[0, 1, 4], [9, 16, 25], [36, 49, 64], [81, 100, 121], [144, 169, 196], [225, 256, 289], [324, 361]]

>>> [''.join(s) for s in split_every(6, 'Hello world')]
['Hello ', 'world']

>>> list(split_every(100, []))
[]
Run Code Online (Sandbox Code Playgroud)

  • 请参阅我的答案,了解基于此版本的无状态单行版本. (2认同)

Ell*_*ron 20

这是一个快速的单行版本.像Haskell一样,它很懒惰.

from itertools import islice, takewhile, repeat
split_every = (lambda n, it:
    takewhile(bool, (list(islice(it, n)) for _ in repeat(None))))
Run Code Online (Sandbox Code Playgroud)

这要求您iter 致电使用split_every.

例:

list(split_every(5, iter(xrange(9))))
[[0, 1, 2, 3, 4], [5, 6, 7, 8]]
Run Code Online (Sandbox Code Playgroud)

虽然不是单行,但以下版本并不要求您调用iter哪个可能是常见的陷阱.

from itertools import islice, takewhile, repeat

def split_every(n, iterable):
    """
    Slice an iterable into chunks of n elements
    :type n: int
    :type iterable: Iterable
    :rtype: Iterator
    """
    iterator = iter(iterable)
    return takewhile(bool, (list(islice(iterator, n)) for _ in repeat(None)))
Run Code Online (Sandbox Code Playgroud)

(感谢@ eli-korvigo的改进.)


pyl*_*ang 9

more_itertools有一个chunked功能:

import more_itertools as mit


list(mit.chunked(range(9), 5))
# [[0, 1, 2, 3, 4], [5, 6, 7, 8]]
Run Code Online (Sandbox Code Playgroud)


acu*_*ner 8

建立一个已接受的答案并使用一个鲜为人知的用法iter(当传递第二个arg时,它会调用第一个arg直到它收到第二个),你可以很容易地做到这一点:

python3:

from itertools import islice

def split_every(n, iterable):
    iterable = iter(iterable)
    yield from iter(lambda: list(islice(iterable, n)), [])
Run Code Online (Sandbox Code Playgroud)

python2:

def split_every(n, iterable):
    iterable = iter(iterable)
    for chunk in iter(lambda: list(islice(iterable, n)), []):
        yield chunk
Run Code Online (Sandbox Code Playgroud)


小智 7

我也遇到了这个问题,因为我也试图切碎批次,但是在流中的生成器上执行此操作,因此这里的大多数解决方案都不适用,或者在 python 3 中不起作用。

对于仍然遇到这个问题的人,这里有一个使用 itertools 的通用解决方案:

from itertools import islice, chain

def iter_in_slices(iterator, size=None):
    while True:
        slice_iter = islice(iterator, size)
        # If no first object this is how StopIteration is triggered
        peek = next(slice_iter)
        # Put the first object back and return slice
        yield chain([peek], slice_iter)
Run Code Online (Sandbox Code Playgroud)