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 x
Ejemplo 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_longest
conizip
, 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 unaStopIteration
vezsourceiter
, 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 StopIteration
while
bucle entry:
/except StopIteration: return
para 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
chunked
encuentra entre ellas.fuente
ichunked
iterables.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
len
y 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
len
función y se agotan:def batcher(iterable, batch_size): while batch := list(islice(iterable, batch_size)): yield batch
Uso 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
batcher
acepta 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 elwhile
ciclo.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
reduce
funció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_batches
A 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_batches
Ejemplos:
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: return
fuente