Возможный дубликат:
как в 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]
batch = (tuple(filterfalse(lambda x: x is None, group)) for group in zip_longest(fillvalue=None, *[iter(iterable)] * n))
Ответы:
Вероятно, это более эффективно (быстрее)
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]
Он избегает создания новых списков.
источник
min()
кода совершенно правильно!len()
, последовательности естьlen()
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)]
источник
izip_longest
withizip
, который не будет дополнять последние записи, а вместо этого обрезает их, когда некоторые элементы начинают заканчиваться.for x, y in enumerate(grouper(3, xrange(10))): print(x,y)
действительно не заполняет значения, он просто полностью удаляет неполный сегмент.list(zip(*[iter(iterable)] * n))
. Это должен быть самый аккуратный фрагмент кода Python, который я когда-либо видел.Как отмечали другие, предоставленный вами код делает именно то, что вы хотите. Для другого подхода
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)
источник
next()
чтобы одинStopIteration
разsourceiter
был исчерпан, тем самым завершая итератор. Без его вызоваnext
пустые итераторы будут возвращаться бесконечно.batchiter.next()
с ,next(batchiter)
чтобы вышеприведенный код будет работать в Python 3.map(list, batch(xrange(10), 3))
. Выполнение:list(batch(xrange(10), 3)
приведет к неожиданным результатам..next()
надо поменять наnext(..)
, аlist(batch(range(0,10),3))
бросаетRuntimeError: generator raised StopIteration
while
цикл вtry:
/,except StopIteration: return
чтобы исправить последнюю проблему.Я дал только один ответ. Однако теперь я считаю, что лучшим решением будет не писать никаких новых функций. More-itertools включает в себя множество дополнительных инструментов и
chunked
входит в их число.источник
ichunked
дает итерации.Странно, похоже, у меня отлично работает в 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]
источник
Я знаю, что это очень короткий фрагмент кода, который не используется
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)))
источник
Решение для Python 3.8, если вы работаете с итерациями, которые не определяют
len
функцию и устали:def batcher(iterable, batch_size): while batch := list(islice(iterable, 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]
Конечно, можно реализовать и без оператора моржа.
источник
batcher
принимает итератор, а не итератор. Например, это приведет к бесконечному циклу со списком. Вероятно,iterator = iter(iterable)
перед запускомwhile
цикла должна быть строка .Это то, что я использую в своем проекте. Он обрабатывает итерации или списки максимально эффективно.
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()]
источник
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]]
источник
Это будет работать для любого итеративного.
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.
источник
Вот подход с использованием
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]]
источник
Работоспособная версия без новых функций в 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]
источник
Вы можете просто сгруппировать повторяемые элементы по их индексу партии.
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]]
источник
Связанные функции вам могут понадобиться:
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 dataframes (
df.iloc[batch(100,0)]
) или numpy array (array[batch(100,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,)]
источник
я использую
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)]
источник
Продолжайте брать (не более) 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
источник