parts into python list split chunks

python - into - ¿Cómo se divide una lista en trozos de tamaño uniforme?



split list python (30)

Tengo una lista de longitud arbitraria y necesito dividirla en partes iguales y operarla. Hay algunas formas obvias de hacerlo, como mantener un contador y dos listas, y cuando la segunda lista se llene, agréguela a la primera lista y vacíe la segunda lista para la siguiente ronda de datos, pero esto es potencialmente extremadamente costoso.

Me preguntaba si alguien tenía una buena solución a esto para listas de cualquier longitud, por ejemplo, usando generadores.

Estaba buscando algo útil en itertools pero no pude encontrar nada obviamente útil. Aunque podría haberlo perdido.

Pregunta relacionada: ¿Cuál es la forma más "pythonic" de iterar sobre una lista en partes?


Crítica de otras respuestas aquí:

Ninguna de estas respuestas son trozos de tamaño uniforme, todos dejan un trozo runt al final, por lo que no están completamente equilibrados. Si estaba usando estas funciones para distribuir trabajo, ha incorporado la posibilidad de que una termine mucho antes que las otras, por lo que se quedaría sin hacer nada mientras que las otras continuaron trabajando duro.

Por ejemplo, la respuesta principal actual termina con:

[60, 61, 62, 63, 64, 65, 66, 67, 68, 69], [70, 71, 72, 73, 74]]

¡Simplemente odio esa trampa al final!

Otros, como la list(grouper(3, xrange(7))) y chunk(xrange(7), 3) devuelven: [(0, 1, 2), (3, 4, 5), (6, None, None)] . Los None son solo rellenos, y bastante poco elegantes en mi opinión. NO están dividiendo los iterables de manera uniforme.

¿Por qué no podemos dividirlos mejor?

Mi (s) solucion (es)

Aquí hay una solución equilibrada, adaptada de una función que he usado en producción (Nota en Python 3 para reemplazar xrange con range ):

def baskets_from(items, maxbaskets=25): baskets = [[] for _ in xrange(maxbaskets)] # in Python 3 use range for i, item in enumerate(items): baskets[i % maxbaskets].append(item) return filter(None, baskets)

Y creé un generador que hace lo mismo si lo pones en una lista:

def iter_baskets_from(items, maxbaskets=3): ''''''generates evenly balanced baskets from indexable iterable'''''' item_count = len(items) baskets = min(item_count, maxbaskets) for x_i in xrange(baskets): yield [items[y_i] for y_i in xrange(x_i, item_count, baskets)]

Y finalmente, ya que veo que todas las funciones anteriores devuelven los elementos en un orden contiguo (como se dieron):

def iter_baskets_contiguous(items, maxbaskets=3, item_count=None): '''''' generates balanced baskets from iterable, contiguous contents provide item_count if providing a iterator that doesn''t support len() '''''' item_count = item_count or len(items) baskets = min(item_count, maxbaskets) items = iter(items) floor = item_count // baskets ceiling = floor + 1 stepdown = item_count % baskets for x_i in xrange(baskets): length = ceiling if x_i < stepdown else floor yield [items.next() for _ in xrange(length)]

Salida

Para probarlos:

print(baskets_from(xrange(6), 8)) print(list(iter_baskets_from(xrange(6), 8))) print(list(iter_baskets_contiguous(xrange(6), 8))) print(baskets_from(xrange(22), 8)) print(list(iter_baskets_from(xrange(22), 8))) print(list(iter_baskets_contiguous(xrange(22), 8))) print(baskets_from(''ABCDEFG'', 3)) print(list(iter_baskets_from(''ABCDEFG'', 3))) print(list(iter_baskets_contiguous(''ABCDEFG'', 3))) print(baskets_from(xrange(26), 5)) print(list(iter_baskets_from(xrange(26), 5))) print(list(iter_baskets_contiguous(xrange(26), 5)))

Que se imprime:

[[0], [1], [2], [3], [4], [5]] [[0], [1], [2], [3], [4], [5]] [[0], [1], [2], [3], [4], [5]] [[0, 8, 16], [1, 9, 17], [2, 10, 18], [3, 11, 19], [4, 12, 20], [5, 13, 21], [6, 14], [7, 15]] [[0, 8, 16], [1, 9, 17], [2, 10, 18], [3, 11, 19], [4, 12, 20], [5, 13, 21], [6, 14], [7, 15]] [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11], [12, 13, 14], [15, 16, 17], [18, 19], [20, 21]] [[''A'', ''D'', ''G''], [''B'', ''E''], [''C'', ''F'']] [[''A'', ''D'', ''G''], [''B'', ''E''], [''C'', ''F'']] [[''A'', ''B'', ''C''], [''D'', ''E''], [''F'', ''G'']] [[0, 5, 10, 15, 20, 25], [1, 6, 11, 16, 21], [2, 7, 12, 17, 22], [3, 8, 13, 18, 23], [4, 9, 14, 19, 24]] [[0, 5, 10, 15, 20, 25], [1, 6, 11, 16, 21], [2, 7, 12, 17, 22], [3, 8, 13, 18, 23], [4, 9, 14, 19, 24]] [[0, 1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15], [16, 17, 18, 19, 20], [21, 22, 23, 24, 25]]

Observe que el generador contiguo proporciona trozos en los mismos patrones de longitud que los otros dos, pero los elementos están todos en orden, y están divididos de manera uniforme, ya que uno puede dividir una lista de elementos discretos.


Aquí hay un generador que produce los trozos que deseas:

def chunks(l, n): """Yield successive n-sized chunks from l.""" for i in range(0, len(l), n): yield l[i:i + n]

import pprint pprint.pprint(list(chunks(range(10, 75), 10))) [[10, 11, 12, 13, 14, 15, 16, 17, 18, 19], [20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [30, 31, 32, 33, 34, 35, 36, 37, 38, 39], [40, 41, 42, 43, 44, 45, 46, 47, 48, 49], [50, 51, 52, 53, 54, 55, 56, 57, 58, 59], [60, 61, 62, 63, 64, 65, 66, 67, 68, 69], [70, 71, 72, 73, 74]]

Si estás usando Python 2, debes usar xrange() lugar de range() :

def chunks(l, n): """Yield successive n-sized chunks from l.""" for i in xrange(0, len(l), n): yield l[i:i + n]

También puedes simplemente usar la comprensión de lista en lugar de escribir una función. Python 3:

[l[i:i + n] for i in range(0, len(l), n)]

Versión de Python 2:

[l[i:i + n] for i in xrange(0, len(l), n)]


Aquí hay un generador que trabaja en iterables arbitrarios:

def split_seq(iterable, size): it = iter(iterable) item = list(itertools.islice(it, size)) while item: yield item item = list(itertools.islice(it, size))

Ejemplo:

>>> import pprint >>> pprint.pprint(list(split_seq(xrange(75), 10))) [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [10, 11, 12, 13, 14, 15, 16, 17, 18, 19], [20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [30, 31, 32, 33, 34, 35, 36, 37, 38, 39], [40, 41, 42, 43, 44, 45, 46, 47, 48, 49], [50, 51, 52, 53, 54, 55, 56, 57, 58, 59], [60, 61, 62, 63, 64, 65, 66, 67, 68, 69], [70, 71, 72, 73, 74]]


Considere el uso de piezas matplotlib.cbook

por ejemplo:

import matplotlib.cbook as cbook segments = cbook.pieces(np.arange(20), 3) for s in segments: print s


Directamente desde la (antigua) documentación de Python (recetas para itertools):

from itertools import izip, chain, repeat def grouper(n, iterable, padvalue=None): "grouper(3, ''abcdefg'', ''x'') --> (''a'',''b'',''c''), (''d'',''e'',''f''), (''g'',''x'',''x'')" return izip(*[chain(iterable, repeat(padvalue, n-1))]*n)

La versión actual, como sugiere JFSebastian:

#from itertools import izip_longest as zip_longest # for Python 2.x from itertools import zip_longest # for Python 3.x #from six.moves import zip_longest # for both (uses the six compat library) def grouper(n, iterable, padvalue=None): "grouper(3, ''abcdefg'', ''x'') --> (''a'',''b'',''c''), (''d'',''e'',''f''), (''g'',''x'',''x'')" return zip_longest(*[iter(iterable)]*n, fillvalue=padvalue)

Supongo que la máquina del tiempo de Guido funciona, funcionó, funcionará, funcionará, funcionó nuevamente.

Estas soluciones funcionan porque [iter(iterable)]*n (o el equivalente en la versión anterior) crea un iterador, repetido n veces en la lista. izip_longest realiza efectivamente un round-robin de "cada" iterador; Debido a que este es el mismo iterador, es avanzado por cada llamada, lo que resulta en que cada zip-roundrobin genere una tupla de n elementos.


En este punto, creo que necesitamos la función obligatoria anónima-recursiva.

Y = lambda f: (lambda x: x(x))(lambda y: f(lambda *args: y(y)(*args))) chunks = Y(lambda f: lambda n: [n[0][:n[1]]] + f((n[0][n[1]:], n[1])) if len(n[0]) > 0 else [])


En este punto, creo que necesitamos un generador recursivo , por si acaso ...

En Python 2:

def chunks(li, n): if li == []: return yield li[:n] for e in chunks(li[n:], n): yield e

En Python 3:

def chunks(li, n): if li == []: return yield li[:n] yield from chunks(li[n:], n)

Además, en caso de una invasión masiva de Alien, un generador recursivo decorado podría ser útil:

def dec(gen): def new_gen(li, n): for e in gen(li, n): if e == []: return yield e return new_gen @dec def chunks(li, n): yield li[:n] for e in chunks(li[n:], n): yield e


La biblioteca de toolz tiene la función de partition para esto:

from toolz.itertoolz.core import partition list(partition(2, [1, 2, 3, 4])) [(1, 2), (3, 4)]


Me gusta mucho la versión del documento de Python propuesta por tzot y JFSebastian, pero tiene dos defectos:

  • no es muy explícito
  • Normalmente no quiero un valor de relleno en la última parte

Estoy usando esto mucho en mi código:

from itertools import islice def chunks(n, iterable): iterable = iter(iterable) while True: yield tuple(islice(iterable, n)) or iterable.next()

ACTUALIZACIÓN: Una versión perezosa de trozos:

from itertools import chain, islice def chunks(n, iterable): iterable = iter(iterable) while True: yield chain([next(iterable)], islice(iterable, n-1))


Me sorprende que nadie haya pensado en usar la forma de dos argumentos de iter :

from itertools import islice def chunk(it, size): it = iter(it) return iter(lambda: tuple(islice(it, size)), ())

Manifestación:

>>> list(chunk(range(14), 3)) [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13)]

Esto funciona con cualquier iterable y produce resultados perezosamente. Devuelve tuplas en lugar de iteradores, pero creo que, sin embargo, tiene cierta elegancia. Tampoco se rellena; Si desea relleno, una simple variación de lo anterior será suficiente:

from itertools import islice, chain, repeat def chunk_pad(it, size, padval=None): it = chain(iter(it), repeat(padval)) return iter(lambda: tuple(islice(it, size)), (padval,) * size)

Manifestación:

>>> list(chunk_pad(range(14), 3)) [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, None)] >>> list(chunk_pad(range(14), 3, ''a'')) [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, ''a'')]

Al igual que las izip_longest basadas en izip_longest , las anteriores siempre se rellenan. Que yo sepa, no hay una receta de itertools de una o dos líneas para una función que, opcionalmente, rellena. Al combinar los dos enfoques anteriores, este se acerca bastante:

_no_padding = object() def chunk(it, size, padval=_no_padding): if padval == _no_padding: it = iter(it) sentinel = () else: it = chain(iter(it), repeat(padval)) sentinel = (padval,) * size return iter(lambda: tuple(islice(it, size)), sentinel)

Manifestación:

>>> list(chunk(range(14), 3)) [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13)] >>> list(chunk(range(14), 3, None)) [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, None)] >>> list(chunk(range(14), 3, ''a'')) [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, ''a'')]

Creo que este es el trozo más corto propuesto que ofrece relleno opcional.


Otra versión más explícita.

def chunkList(initialList, chunkSize): """ This function chunks a list into sub lists that have a length equals to chunkSize. Example: lst = [3, 4, 9, 7, 1, 1, 2, 3] print(chunkList(lst, 3)) returns [[3, 4, 9], [7, 1, 1], [2, 3]] """ finalList = [] for i in range(0, len(initialList), chunkSize): finalList.append(initialList[i:i+chunkSize]) return finalList


Sé que es un poco viejo, pero no sé por qué nadie mencionó numpy.array_split :

lst = range(50) In [26]: np.array_split(lst,5) Out[26]: [array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19]), array([20, 21, 22, 23, 24, 25, 26, 27, 28, 29]), array([30, 31, 32, 33, 34, 35, 36, 37, 38, 39]), array([40, 41, 42, 43, 44, 45, 46, 47, 48, 49])]


Si quieres algo super simple:

def chunks(l, n): n = max(1, n) return (l[i:i+n] for i in xrange(0, len(l), n))


Si sabes el tamaño de la lista:

def SplitList(list, chunk_size): return [list[offs:offs+chunk_size] for offs in range(0, len(list), chunk_size)]

Si no lo hace (un iterador):

def IterChunks(sequence, chunk_size): res = [] for item in sequence: res.append(item) if len(res) >= chunk_size: yield res res = [] if res: yield res # yield the last, incomplete, portion

En este último caso, puede reformularse de una manera más hermosa si puede estar seguro de que la secuencia siempre contiene un número entero de fragmentos de un tamaño determinado (es decir, no hay un último fragmento incompleto).



Simple pero elegante

l = range(1, 1000) print [l[x:x+10] for x in xrange(0, len(l), 10)]

o si prefieres:

chunks = lambda l, n: [l[x: x+n] for x in xrange(0, len(l), n)] chunks(l, 10)


Sin llamar a len (), lo que es bueno para listas grandes:

def splitter(l, n): i = 0 chunk = l[:n] while chunk: yield chunk i += n chunk = l[i:i+n]

Y esto es para iterables:

def isplitter(l, n): l = iter(l) chunk = list(islice(l, n)) while chunk: yield chunk chunk = list(islice(l, n))

El sabor funcional de lo anterior:

def isplitter2(l, n): return takewhile(bool, (tuple(islice(start, n)) for start in repeat(iter(l))))

O:

def chunks_gen_sentinel(n, seq): continuous_slices = imap(islice, repeat(iter(seq)), repeat(0), repeat(n)) return iter(imap(tuple, continuous_slices).next,())

O:

def chunks_gen_filter(n, seq): continuous_slices = imap(islice, repeat(iter(seq)), repeat(0), repeat(n)) return takewhile(bool,imap(tuple, continuous_slices))


También puede usar la función utilspie biblioteca utilspie como:

>>> from utilspie import iterutils >>> a = [1, 2, 3, 4, 5, 6, 7, 8, 9] >>> list(iterutils.get_chunks(a, 5)) [[1, 2, 3, 4, 5], [6, 7, 8, 9]]

Puedes instalar utilspie via pip:

sudo pip install utilspie

Descargo de responsabilidad: soy el creador de utilspie library .


Tenía curiosidad por el rendimiento de diferentes enfoques y aquí está:

Probado en Python 3.5.1

import time batch_size = 7 arr_len = 298937 #---------slice------------- print("/r/nslice") start = time.time() arr = [i for i in range(0, arr_len)] while True: if not arr: break tmp = arr[0:batch_size] arr = arr[batch_size:-1] print(time.time() - start) #-----------index----------- print("/r/nindex") arr = [i for i in range(0, arr_len)] start = time.time() for i in range(0, round(len(arr) / batch_size + 1)): tmp = arr[batch_size * i : batch_size * (i + 1)] print(time.time() - start) #----------batches 1------------ def batch(iterable, n=1): l = len(iterable) for ndx in range(0, l, n): yield iterable[ndx:min(ndx + n, l)] print("/r/nbatches 1") arr = [i for i in range(0, arr_len)] start = time.time() for x in batch(arr, batch_size): tmp = x print(time.time() - start) #----------batches 2------------ from itertools import islice, chain def batch(iterable, size): sourceiter = iter(iterable) while True: batchiter = islice(sourceiter, size) yield chain([next(batchiter)], batchiter) print("/r/nbatches 2") arr = [i for i in range(0, arr_len)] start = time.time() for x in batch(arr, batch_size): tmp = x print(time.time() - start) #---------chunks------------- def chunks(l, n): """Yield successive n-sized chunks from l.""" for i in range(0, len(l), n): yield l[i:i + n] print("/r/nchunks") arr = [i for i in range(0, arr_len)] start = time.time() for x in chunks(arr, batch_size): tmp = x print(time.time() - start) #-----------grouper----------- from itertools import zip_longest # for Python 3.x #from six.moves import zip_longest # for both (uses the six compat library) def grouper(iterable, n, padvalue=None): "grouper(3, ''abcdefg'', ''x'') --> (''a'',''b'',''c''), (''d'',''e'',''f''), (''g'',''x'',''x'')" return zip_longest(*[iter(iterable)]*n, fillvalue=padvalue) arr = [i for i in range(0, arr_len)] print("/r/ngrouper") start = time.time() for x in grouper(arr, batch_size): tmp = x print(time.time() - start)

Resultados:

slice 31.18285083770752 index 0.02184295654296875 batches 1 0.03503894805908203 batches 2 0.22681021690368652 chunks 0.019841909408569336 grouper 0.006506919860839844


Una expresión generadora:

def chunks(seq, n): return (seq[i:i+n] for i in xrange(0, len(seq), n))

p.ej.

print list(chunks(range(1, 1000), 10))


Vi la respuesta más asombrosa de Python-ish en un duplicate de esta pregunta:

from itertools import zip_longest a = range(1, 16) i = iter(a) r = list(zip_longest(i, i, i)) >>> print(r) [(1, 2, 3), (4, 5, 6), (7, 8, 9), (10, 11, 12), (13, 14, 15)]

Puedes crear n-tuple para cualquier n. Si a = range(1, 15) , entonces el resultado será:

[(1, 2, 3), (4, 5, 6), (7, 8, 9), (10, 11, 12), (13, 14, None)]

Si la lista está dividida en partes iguales, entonces puede reemplazar zip_longest con zip , de lo contrario se zip_longest el triplete (13, 14, None) . Python 3 se utiliza anteriormente. Para Python 2, use izip_longest .


código:

def split_list(the_list, chunk_size): result_list = [] while the_list: result_list.append(the_list[:chunk_size]) the_list = the_list[chunk_size:] return result_list a_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] print split_list(a_list, 3)

resultado:

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


je, una versión de línea

In [48]: chunk = lambda ulist, step: map(lambda i: ulist[i:i+step], xrange(0, len(ulist), step)) In [49]: chunk(range(1,100), 10) Out[49]: [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [11, 12, 13, 14, 15, 16, 17, 18, 19, 20], [21, 22, 23, 24, 25, 26, 27, 28, 29, 30], [31, 32, 33, 34, 35, 36, 37, 38, 39, 40], [41, 42, 43, 44, 45, 46, 47, 48, 49, 50], [51, 52, 53, 54, 55, 56, 57, 58, 59, 60], [61, 62, 63, 64, 65, 66, 67, 68, 69, 70], [71, 72, 73, 74, 75, 76, 77, 78, 79, 80], [81, 82, 83, 84, 85, 86, 87, 88, 89, 90], [91, 92, 93, 94, 95, 96, 97, 98, 99]]


Me doy cuenta de que esta pregunta es antigua (tropezó con ella en Google), pero seguramente algo como lo siguiente es mucho más simple y claro que cualquiera de las enormes sugerencias complejas y solo utiliza el corte en rebanadas:

def chunker(iterable, chunksize): for i,c in enumerate(iterable[::chunksize]): yield iterable[i*chunksize:(i+1)*chunksize] >>> for chunk in chunker(range(0,100), 10): ... print list(chunk) ... [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [10, 11, 12, 13, 14, 15, 16, 17, 18, 19] [20, 21, 22, 23, 24, 25, 26, 27, 28, 29] ... etc ...


Una solución más

def make_chunks(data, chunk_size): while data: chunk, data = data[:chunk_size], data[chunk_size:] yield chunk >>> for chunk in make_chunks([1, 2, 3, 4, 5, 6, 7], 2): ... print chunk ... [1, 2] [3, 4] [5, 6] [7] >>>


[AA[i:i+SS] for i in range(len(AA))[::SS]]

Donde AA es matriz, SS es tamaño de trozo. Por ejemplo:

>>> AA=range(10,21);SS=3 >>> [AA[i:i+SS] for i in range(len(AA))[::SS]] [[10, 11, 12], [13, 14, 15], [16, 17, 18], [19, 20]] # or [range(10, 13), range(13, 16), range(16, 19), range(19, 21)] in py3


a = [1, 2, 3, 4, 5, 6, 7, 8, 9] CHUNK = 4 [a[i*CHUNK:(i+1)*CHUNK] for i in xrange((len(a) + CHUNK - 1) / CHUNK )]


def chunk(input, size): return map(None, *([iter(input)] * size))


def chunks(iterable,n): """assumes n is an integer>0 """ iterable=iter(iterable) while True: result=[] for i in range(n): try: a=next(iterable) except StopIteration: break else: result.append(a) if result: yield result else: break g1=(i*i for i in range(10)) g2=chunks(g1,3) print g2 ''<generator object chunks at 0x0337B9B8>'' print list(g2) ''[[0, 1, 4], [9, 16, 25], [36, 49, 64], [81]]''


def split_seq(seq, num_pieces): start = 0 for i in xrange(num_pieces): stop = start + len(seq[i::num_pieces]) yield seq[start:stop] start = stop

uso:

seq = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] for seq in split_seq(seq, 3): print seq