как разделить итерируемое на куски постоянного размера - PullRequest
60 голосов
/ 28 ноября 2011

Возможный дубликат:
Как разбить список на куски одинакового размера в 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.Кто-нибудь захочет указать мне правильное направление?

[Edit: я в конечном итоге понял, что вышеупомянутое поведение происходит только тогда, когда я запускаю это в ipython, а не сам Python]

Ответы [ 7 ]

89 голосов
/ 28 ноября 2011

Это, вероятно, более эффективно (быстрее)

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

Это позволяет избежать создания новых списков.

33 голосов
/ 28 ноября 2011

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)

Работает так:

>>> list(grouper(3, range(10)))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, None, None)]
24 голосов
/ 28 ноября 2011

Как уже отмечали другие, код, который вы дали, делает именно то, что вы хотите.Для другого подхода с использованием 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)
7 голосов
/ 28 ноября 2011

Странно, у меня в Python 2.х отлично работает 100x

>>> 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]
1 голос
/ 15 февраля 2019

Вот подход с использованием функции reduce.

Oneliner:

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]]
0 голосов
/ 31 января 2019

Это будет работать для любой итерации.

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: не будет работать, если итерация имеет значения None.

0 голосов
/ 30 января 2019

Это то, что я использую в своем проекте.Он обрабатывает итерации или списки настолько эффективно, насколько это возможно.

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