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).
Si tuvieras un tamaño de trozo de 3 por ejemplo, podrías hacer:
zip(*[iterable[i::3] for i in range(3)])
fuente: http://code.activestate.com/recipes/303060-group-a-list-into-sequential-n-tuples/
Usaría esto cuando mi tamaño de fragmento sea un número fijo que puedo escribir, por ejemplo, ''3'', y nunca cambiaría.
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