Posible duplicado:
¿Cómo se divide una lista en fragmentos de tamaño uniforme en Python?
Me sorprende no haber podido encontrar una función "por lotes" que tome como entrada un iterable y devuelva un iterable de iterables.
Por ejemplo:
for i in batch(range(0,10), 1): print i
[0]
[1]
...
[9]
o:
for i in batch(range(0,10), 3): print i
[0,1,2]
[3,4,5]
[6,7,8]
[9]
Ahora, escribí lo que pensé que era un generador bastante simple:
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
Pero lo anterior no me da lo que hubiera esperado:
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]
Entonces, me he perdido algo y esto probablemente muestra mi total falta de comprensión de los generadores de Python. ¿A alguien le importaría indicarme la dirección correcta?
[Editar: finalmente me di cuenta de que el comportamiento anterior ocurre solo cuando ejecuto esto dentro de ipython en lugar de python en sí]

batch = (tuple(filterfalse(lambda x: x is None, group)) for group in zip_longest(fillvalue=None, *[iter(iterable)] * n))Respuestas:
Probablemente sea más eficiente (más rápido)
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 xEjemplo usando lista
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]Evita la creación de nuevas listas.
fuente
min()código es completamente correcto!len(), las secuencias tienenlen()FWIW, las recetas en el módulo itertools proporcionan este ejemplo:
def grouper(n, iterable, fillvalue=None): "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx" args = [iter(iterable)] * n return zip_longest(fillvalue=fillvalue, *args)Funciona así:
>>> list(grouper(3, range(10))) [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, None, None)]fuente
izip_longestconizip, que no rellenará las últimas entradas, sino que cortará las entradas cuando algunos de los elementos comiencen a agotarse.for x, y in enumerate(grouper(3, xrange(10))): print(x,y)hecho no completa los valores, simplemente elimina el segmento incompleto por completo.list(zip(*[iter(iterable)] * n)). Este tiene que ser el código Python más bonito que he visto en mi vida.Como han señalado otros, el código que ha proporcionado hace exactamente lo que desea. Para otro enfoque
itertools.islice, puede ver un ejemplo de la siguiente receta:from itertools import islice, chain def batch(iterable, size): sourceiter = iter(iterable) while True: batchiter = islice(sourceiter, size) yield chain([batchiter.next()], batchiter)fuente
next()para hacer que se agote unaStopIterationvezsourceiter, terminando así el iterador. Sin la llamadanext, continuaría devolviendo iteradores vacíos indefinidamente.batchiter.next()connext(batchiter)para que el código anterior funcione en Python 3.map(list, batch(xrange(10), 3)). Hacer:list(batch(xrange(10), 3)producirá resultados inesperados..next()debe cambiarse anext(..)ylist(batch(range(0,10),3))arrojaRuntimeError: generator raised StopIterationwhilebucle entry:/except StopIteration: returnpara solucionar este último problema.Solo di una respuesta. Sin embargo, ahora creo que la mejor solución podría ser no escribir funciones nuevas. More-itertools incluye muchas herramientas adicionales y se
chunkedencuentra entre ellas.fuente
ichunkediterables.Extraño, parece funcionar bien para mí en 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]fuente
Este es un fragmento de código muy corto que sé que no se usa
leny funciona tanto en Python 2 como en 3 (no es mi creación):def chunks(iterable, size): from itertools import chain, islice iterator = iter(iterable) for first in iterator: yield list(chain([first], islice(iterator, size - 1)))fuente
Solución para Python 3.8 si está trabajando con iterables que no definen una
lenfunción y se agotan:def batcher(iterable, batch_size): while batch := list(islice(iterable, batch_size)): yield batchUso de ejemplo:
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]Por supuesto, también podría implementarse sin el operador de morsa.
fuente
batcheracepta un iterador, no un iterable. Daría como resultado un bucle infinito con una lista, por ejemplo. Probablemente debería haber una líneaiterator = iter(iterable)antes de comenzar elwhileciclo.Esto es lo que uso en mi proyecto. Maneja iterables o listas de la manera más eficiente posible.
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()]fuente
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]]fuente
Esto funcionaría para cualquier iterable.
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))Funcionaría así:
>>>list(batch_iterable(range(0,5)), 2) [(0, 1), (2, 3), (4,)]PD: No funcionaría si iterable tiene valores None.
fuente
Aquí hay un enfoque que usa la
reducefunción.Un trazador de líneas:
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, [[]])O una versión más legible:
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, [[]])Prueba:
>>> 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]]fuente
Puede agrupar elementos iterables por su índice de lote.
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_batchesA menudo es el caso cuando desea recopilar iterables internos, por lo que aquí hay una versión más avanzada.
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_batchesEjemplos:
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]]fuente
Funcionalidad relacionada que puede necesitar:
def batch(size, i): """ Get the i'th batch of the given size """ return slice(size* i, size* i + size)Uso:
>>> [1,2,3,4,5,6,7,8,9,10][batch(3, 1)] >>> [4, 5, 6]Obtiene el primer lote de la secuencia y también puede funcionar con otras estructuras de datos, como pandas dataframes (
df.iloc[batch(100,0)]) o numpy array (array[batch(100,0)]).fuente
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,)]fuente
yo suelo
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)]fuente
Siga tomando (como máximo) n elementos hasta que se acabe.
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: returnfuente