如何在常量大小的块中拆分可迭代的

mat*_*ieu 68 python algorithm generator chunking

可能重复:
如何在Python中将列表拆分为大小均匀的块?

我很惊讶我找不到一个"批处理"函数,它将输入迭代并返回一个可迭代的迭代.

例如:

for i in batch(range(0,10), 1): print i
[0]
[1]
...
[9]
Run Code Online (Sandbox Code Playgroud)

要么:

for i in batch(range(0,10), 3): print i
[0,1,2]
[3,4,5]
[6,7,8]
[9]
Run Code Online (Sandbox Code Playgroud)

现在,我写了一个我认为非常简单的生成器:

def batch(iterable, n = 1):
   current_batch = []
   for item in iterable:
       current_batch.append(item)
       if len(current_batch) == n:
           yield current_batch
           current_batch = []
   if current_batch:
       yield current_batch
Run Code Online (Sandbox Code Playgroud)

但上面没有给我我所期望的:

for x in   batch(range(0,10),3): print x
[0]
[0, 1]
[0, 1, 2]
[3]
[3, 4]
[3, 4, 5]
[6]
[6, 7]
[6, 7, 8]
[9]
Run Code Online (Sandbox Code Playgroud)

所以,我错过了一些东西,这可能表明我完全缺乏对python生成器的理解.有人会关心我指向正确的方向吗?

[编辑:我最终意识到只有当我在ipython而不是python本身中运行时才会发生上述行为]

Car*_* F. 92

这可能更有效(更快)

def batch(iterable, n=1):
    l = len(iterable)
    for ndx in range(0, l, n):
        yield iterable[ndx:min(ndx + n, l)]

for x in batch(range(0, 10), 3):
    print x
Run Code Online (Sandbox Code Playgroud)

它避免了构建新列表.

  • 你的批处理实际上接受一个列表(用len()),不可迭代(没有len()) (58认同)
  • 这更快,因为它不是解决问题的方法.Raymond Hettinger的石斑鱼配方 - 目前在此之下 - 是您正在寻找的通用解决方案,不需要输入对象具有__len__方法. (22认同)
  • [Iterables](https://docs.python.org/3/glossary.html#term-iterable)没有`len()`,[序列](https://docs.python.org/3/ glossary.html#term-sequence)有`len()` (12认同)
  • 你为什么用min()?没有`min()`代码是完全正确的! (4认同)
  • 根据记录,这是我发现的最快的解决方案:我的= 4.5s,你的= 0.43s,Donkopotamus = 14.8s (3认同)
  • 这可能不是最通用的解决方案。但它很快,而且不返回“None”。上面的示例生成 [0, 1, 2] [3, 4, 5] [6, 7, 8] [9] (2认同)

Ray*_*ger 37

FWIW,itertools模块中配方提供了这个例子:

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)

它的工作原理如下:

>>> list(grouper(3, range(10)))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, None, None)]
Run Code Online (Sandbox Code Playgroud)

  • 这不是我所需要的,因为它用一组None填充最后一个元素.ie,None是我实际用于我的函数的数据中的有效值,所以我需要的是不填充最后一个条目的东西. (9认同)
  • @mathieu用`izip`替换`izip_longest`,它不会填充最后的条目,而是在某些元素开始耗尽时切断条目. (9认同)
  • @GoogieK`为X,Y在枚举(石斑鱼(3,x范围(10))):打印(X,Y)`确实不填充值,它只是丢弃该不完整的段共. (5认同)
  • 作为一个衬垫,如果不完整,则删除最后一个元素:“list(zip(*[iter(iterable)] * n))”。这一定是我见过的最简洁的 Python 代码了。 (4认同)
  • 在python 3中应为zip_longest / zip (2认同)
  • @PeterGerdes除非输入迭代已经具有一些可利用的结构(即重塑一个numpy数组),否则即使对于大块大小,此解决方案也应接近最佳.它以C-speed运行,调用迭代器尽可能快地填充元组元素,并尽可能重用输出元组. (2认同)

don*_*mus 25

正如其他人所说,您提供的代码完全符合您的要求.对于另一种使用方法,itertools.islice您可以看到以下配方的示例:

from itertools import islice, chain

def batch(iterable, size):
    sourceiter = iter(iterable)
    while True:
        batchiter = islice(sourceiter, size)
        yield chain([batchiter.next()], batchiter)
Run Code Online (Sandbox Code Playgroud)

  • 我不得不用`next(batchiter)`替换`batchiter.next()`以使上面的代码在Python 3中运行. (3认同)
  • 不适用于 py3。`.next()` 必须更改为 `next(..)`,并且 `list(batch(range(0,10),3))` 抛出 `RuntimeError: Generator raise StopIteration` (3认同)
  • @abhilash 否...此代码使用对“next()”的调用来在“sourceiter”耗尽后引发“StopIteration”,从而终止迭代器。如果没有调用“next”,它将继续无限期地返回空迭代器。 (2认同)
  • 指出链接文章中的评论:“您应该添加警告,必须先完全消耗一批,然后才能继续进行下一个。” 它的输出应使用类似“ map(list,batch(xrange(10),3))”的形式使用。这样做:`list(batch(xrange(10),3)`会产生意外的结果。 (2认同)
  • @mathieu:将 `while` 循环包装在 `try:`/` except StopIteration: return` 中以解决后一个问题。 (2认同)

Yon*_* Wu 22

More-itertools包括两个功能,可以满足您的需求:

  • 从 python 3.12 开始,标准的 `itertools` 包实现了批处理函数 https://docs.python.org/3.12/library/itertools.html#itertools.batched (8认同)
  • 这确实是最合适的答案(尽管它需要再安装一个包),而且还有“ichunked”可以产生可迭代对象。 (2认同)

Yon*_* Wu 10

这是一个非常短的代码片段,我知道它len在 Python 2 和 3(不是我的创作)下不使用和工作:

def chunks(iterable, size):
    from itertools import chain, islice
    iterator = iter(iterable)
    for first in iterator:
        yield list(chain([first], islice(iterator, size - 1)))
Run Code Online (Sandbox Code Playgroud)


Joh*_*Doe 9

很奇怪,似乎在Python 2.x中对我很好

>>> def batch(iterable, n = 1):
...    current_batch = []
...    for item in iterable:
...        current_batch.append(item)
...        if len(current_batch) == n:
...            yield current_batch
...            current_batch = []
...    if current_batch:
...        yield current_batch
...
>>> for x in batch(range(0, 10), 3):
...     print x
...
[0, 1, 2]
[3, 4, 5]
[6, 7, 8]
[9]
Run Code Online (Sandbox Code Playgroud)


Atr*_*ami 8

Python 3.8 的解决方案,如果您正在使用未定义len函数的可迭代对象,并且感到筋疲力尽:

from itertools import islice

def batcher(iterable, batch_size):
    iterator = iter(iterable)
    while batch := list(islice(iterator, batch_size)):
        yield batch
Run Code Online (Sandbox Code Playgroud)

用法示例:

def my_gen():
    yield from range(10)
 
for batch in batcher(my_gen(), 3):
    print(batch)

>>> [0, 1, 2]
>>> [3, 4, 5]
>>> [6, 7, 8]
>>> [9]

Run Code Online (Sandbox Code Playgroud)

当然也可以在没有海象运算符的情况下实现。

  • 在当前版本中,“batcher”接受迭代器,而不是可迭代对象。例如,这会导致列表的无限循环。在开始“while”循环之前可能应该有一行“iterator = iter(iterable)”。 (7认同)

小智 5

python 3.8 中没有新功能的可行版本,改编自 @Atra Azami 的答案。

import itertools    

def batch_generator(iterable, batch_size=1):
    iterable = iter(iterable)

    while True:
        batch = list(itertools.islice(iterable, batch_size))
        if len(batch) > 0:
            yield batch
        else:
            break

for x in batch_generator(range(0, 10), 3):
    print(x)
Run Code Online (Sandbox Code Playgroud)

输出:

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


0-_*_*_-0 5

我喜欢这一个,

def batch(x, bs):
    return [x[i:i+bs] for i in range(0, len(x), bs)]
Run Code Online (Sandbox Code Playgroud)

这会返回 size 的批次列表,当然bs您可以使用生成器表达式将其设为生成器。(i for i in iterable)