116

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

我很惊讶我找不到将可迭代作为输入并返回可迭代的可迭代的“批处理”函数。

例如:

for i in batch(range(0,10), 1): print i
[0]
[1]
...
[9]

或者:

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

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

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]
[0, 1]
[0, 1, 2]
[3]
[3, 4]
[3, 4, 5]
[6]
[6, 7]
[6, 7, 8]
[9]

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

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

4

20 回答 20

162

这可能更有效(更快)

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

使用列表的示例

data = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # list of data 

for x in batch(data, 3):
    print(x)

# Output

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

它避免了构建新列表。

于 2011-11-28T01:12:42.920 回答
71

FWIW,itertools 模块中的食谱提供了这个例子:

def grouper(n, iterable, fillvalue=None):
    "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return zip_longest(fillvalue=fillvalue, *args)

它是这样工作的:

>>> list(grouper(3, range(10)))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, None, None)]
于 2011-11-28T01:09:29.467 回答
34

正如其他人所指出的,您提供的代码正是您想要的。对于另一种使用方法,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)
于 2011-11-28T01:13:20.293 回答
34

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

于 2019-07-29T03:50:24.120 回答
13

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

示例用法:

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]

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

于 2020-07-15T11:29:56.123 回答
10

奇怪,在 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]
于 2011-11-28T01:05:15.260 回答
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)))
于 2019-07-29T03:07:44.150 回答
4

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)

输出:

[0, 1, 2]
[3, 4, 5]
[6, 7, 8]
[9]
于 2020-11-26T22:32:46.220 回答
4

通过利用 islice 和 iter(callable) 行为,尽可能多地进入 CPython:

from itertools import islice

def chunked(generator, size):
    """Read parts of the generator, pause each time after a chunk"""
    # islice returns results until 'size',
    # make_chunk gets repeatedly called by iter(callable).
    gen = iter(generator)
    make_chunk = lambda: list(islice(gen, size))
    return iter(make_chunk, [])

受 more-itertools 的启发,并简化为该代码的本质。

于 2021-02-25T09:08:30.650 回答
3
def batch(iterable, n):
    iterable=iter(iterable)
    while True:
        chunk=[]
        for i in range(n):
            try:
                chunk.append(next(iterable))
            except StopIteration:
                yield chunk
                return
        yield chunk

list(batch(range(10), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
于 2019-08-20T16:33:30.830 回答
3

我喜欢这一个,

def batch(x, bs):
    return [x[i:i+bs] for i in range(0, len(x), bs)]

这将返回一个批量大小的列表bs,您当然可以使用生成器表达式使其成为生成器(i for i in iterable)

于 2021-06-21T12:37:45.587 回答
2

这是我在我的项目中使用的。它尽可能高效地处理迭代或列表。

def chunker(iterable, size):
    if not hasattr(iterable, "__len__"):
        # generators don't have len, so fall back to slower
        # method that works with generators
        for chunk in chunker_gen(iterable, size):
            yield chunk
        return

    it = iter(iterable)
    for i in range(0, len(iterable), size):
        yield [k for k in islice(it, size)]


def chunker_gen(generator, size):
    iterator = iter(generator)
    for first in iterator:

        def chunk():
            yield first
            for more in islice(iterator, size - 1):
                yield more

        yield [k for k in chunk()]
于 2019-01-29T23:55:44.433 回答
2

这是一种使用reduce函数的方法。

单线:

from functools import reduce
reduce(lambda cumulator,item: cumulator[-1].append(item) or cumulator if len(cumulator[-1]) < batch_size else cumulator + [[item]], input_array, [[]])

或更易读的版本:

from functools import reduce
def batch(input_list, batch_size):
  def reducer(cumulator, item):
    if len(cumulator[-1]) < batch_size:
      cumulator[-1].append(item)
      return cumulator
    else:
      cumulator.append([item])
    return cumulator
  return reduce(reducer, input_list, [[]])

测试:

>>> batch([1,2,3,4,5,6,7], 3)
[[1, 2, 3], [4, 5, 6], [7]]
>>> batch(a, 8)
[[1, 2, 3, 4, 5, 6, 7]]
>>> batch([1,2,3,None,4], 3)
[[1, 2, 3], [None, 4]]
于 2019-02-15T05:46:29.547 回答
1

这适用于任何可迭代的。

from itertools import zip_longest, filterfalse

def batch_iterable(iterable, batch_size=2): 
    args = [iter(iterable)] * batch_size 
    return (tuple(filterfalse(lambda x: x is None, group)) for group in zip_longest(fillvalue=None, *args))

它会像这样工作:

>>>list(batch_iterable(range(0,5)), 2)
[(0, 1), (2, 3), (4,)]

PS:如果 iterable 没有 None 值,它将不起作用。

于 2019-01-31T00:15:49.677 回答
0

您可以按批次索引对可迭代项目进行分组。

def batch(items: Iterable, batch_size: int) -> Iterable[Iterable]:
    # enumerate items and group them by batch index
    enumerated_item_groups = itertools.groupby(enumerate(items), lambda t: t[0] // batch_size)
    # extract items from enumeration tuples
    item_batches = ((t[1] for t in enumerated_items) for key, enumerated_items in enumerated_item_groups)
    return item_batches

当您想要收集内部迭代时通常是这种情况,所以这里有更高级的版本。

def batch_advanced(items: Iterable, batch_size: int, batches_mapper: Callable[[Iterable], Any] = None) -> Iterable[Iterable]:
    enumerated_item_groups = itertools.groupby(enumerate(items), lambda t: t[0] // batch_size)
    if batches_mapper:
        item_batches = (batches_mapper(t[1] for t in enumerated_items) for key, enumerated_items in enumerated_item_groups)
    else:
        item_batches = ((t[1] for t in enumerated_items) for key, enumerated_items in enumerated_item_groups)
    return item_batches

例子:

print(list(batch_advanced([1, 9, 3, 5, 2, 4, 2], 4, tuple)))
# [(1, 9, 3, 5), (2, 4, 2)]
print(list(batch_advanced([1, 9, 3, 5, 2, 4, 2], 4, list)))
# [[1, 9, 3, 5], [2, 4, 2]]
于 2020-02-25T22:51:00.970 回答
0

您可能需要的相关功能:

def batch(size, i):
    """ Get the i'th batch of the given size """
    return slice(size* i, size* i + size)

用法:

>>> [1,2,3,4,5,6,7,8,9,10][batch(3, 1)]
>>> [4, 5, 6]

它从序列中获取第 i 个批次,并且它也可以与其他数据结构一起使用,例如 pandas 数据帧 ( df.iloc[batch(100,0)]) 或 numpy 数组 ( array[batch(100,0)])。

于 2020-03-19T14:28:21.273 回答
0
from itertools import *

class SENTINEL: pass

def batch(iterable, n):
    return (tuple(filterfalse(lambda x: x is SENTINEL, group)) for group in zip_longest(fillvalue=SENTINEL, *[iter(iterable)] * n))

print(list(range(10), 3)))
# outputs: [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9,)]
print(list(batch([None]*10, 3)))
# outputs: [(None, None, None), (None, None, None), (None, None, None), (None,)]
于 2020-05-27T12:54:42.900 回答
0

我用

def batchify(arr, batch_size):
  num_batches = math.ceil(len(arr) / batch_size)
  return [arr[i*batch_size:(i+1)*batch_size] for i in range(num_batches)]
  
于 2020-08-21T14:16:55.037 回答
0

继续取(最多)n 个元素,直到用完为止。

def chop(n, iterable):
    iterator = iter(iterable)
    while chunk := list(take(n, iterator)):
        yield chunk


def take(n, iterable):
    iterator = iter(iterable)
    for i in range(n):
        try:
            yield next(iterator)
        except StopIteration:
            return
于 2020-11-14T08:11:09.877 回答
0

该代码具有以下特点:

  • 可以将列表或生成器(无 len())作为输入
  • 不需要导入其他包
  • 最后一批没有添加填充
def batch_generator(items, batch_size):
    itemid=0 # Keeps track of current position in items generator/list
    batch = [] # Empty batch
    for item in items: 
      batch.append(item) # Append items to batch
      if len(batch)==batch_size:
        yield batch
        itemid += batch_size # Increment the position in items
        batch = []
    yield batch # yield last bit
于 2021-03-18T12:43:28.683 回答