¿Cómo se divide una lista en partes iguales?
Frecuentes
Visto 1,111 veces
2485
Tengo una lista de longitud arbitraria y necesito dividirla en trozos de igual tamaño y operar en ella. Hay algunas formas obvias de hacer esto, 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 “pitónica” de iterar sobre una lista en trozos?
30 Respuestas
3534
Aquí hay un generador que produce los fragmentos que desea:
def chunks(lst, n):
"""Yield successive n-sized chunks from lst."""
for i in range(0, len(lst), n):
yield lst[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á usando Python 2, debe usar xrange()
en lugar de range()
:
def chunks(lst, n):
"""Yield successive n-sized chunks from lst."""
for i in xrange(0, len(lst), n):
yield lst[i:i + n]
También puede simplemente usar la comprensión de listas en lugar de escribir una función, aunque es una buena idea encapsular operaciones como esta en funciones con nombre para que su código sea más fácil de entender. Python 3:
[lst[i:i + n] for i in range(0, len(lst), n)]
Versión de Python 2:
[lst[i:i + n] for i in xrange(0, len(lst), n)]
respondido 28 nov., 19:01
¿Qué sucede si no podemos saber la longitud de la lista? Pruebe esto en itertools.repeat ([1, 2, 3]), por ejemplo: jespern
Esa es una extensión interesante de la pregunta, pero la pregunta original claramente se refería a operar en una lista. - Ned Batchelder
estas funciones deben estar en la maldita biblioteca estándar - dgan
@Calimo: ¿qué sugieres? Te entrego una lista con 47 elementos. ¿Cómo le gustaría dividirlo en "trozos de tamaño uniforme"? El OP aceptó la respuesta, por lo que está claramente de acuerdo con el último trozo de tamaño diferente. ¿Quizás la frase en inglés es imprecisa? - Ned Batchelder
@NedBatchelder Estoy de acuerdo en que la pregunta está bastante mal definida, pero puede dividir una lista de 47 elementos en 5 fragmentos de 9, 9, 9, 10 y 10 elementos, en lugar de 7, 10, 10, 10 y 10. Es no exactamente uniforme, pero eso es lo que tenía en mente cuando busqué en Google las palabras clave "partes iguales". Esto significa que necesita n para definir el número de fragmentos, no su tamaño. Otra respuesta a continuación sugiere una forma de hacerlo realmente. Su respuesta es básicamente la misma que la de la "pregunta relacionada" vinculada. - calimo
589
Si quieres algo súper simple:
def chunks(l, n):
n = max(1, n)
return (l[i:i+n] for i in range(0, len(l), n))
Utilice la herramienta xrange()
en lugar de range()
en el caso de Python 2.x
Respondido el 06 de enero de 20 a las 10:01
O (si estamos haciendo diferentes representaciones de esta función en particular) podría definir una función lambda a través de: lambda x, y: [x [i: i + y] para i en el rango (0, len (x), y) ]. ¡Me encanta este método de comprensión de listas! - JP
después del regreso debe haber [, no (- alwbtc
"Súper simple" significa no tener que depurar bucles infinitos. Felicitaciones por el max()
. - bob stein
no hay nada simple en esta solución - con
@Nhoj_Gonk Vaya, no es un bucle infinito, pero los fragmentos (L, 0) generarían un ValueError sin max (). En cambio, max () convierte cualquier cosa menor a 1 en 1. - bob stein
310
Directamente de 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, sugerida por 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á, habrá funcionado, volvió a funcionar.
Estas soluciones funcionan porque [iter(iterable)]*n
(o el equivalente en la versión anterior) crea . iterador, repetido n
veces en la lista. izip_longest
luego realiza efectivamente un round-robin de "cada" iterador; debido a que este es el mismo iterador, cada llamada lo hace avanzar, lo que da como resultado que cada una n
los productos.
Respondido el 21 de Septiembre de 17 a las 10:09
@ninjagecko: list(grouper(3, range(10)))
devoluciones [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, None, None)]
, y todas las tuplas tienen una longitud de 3. Por favor, amplíe su comentario porque no puedo entenderlo; como llamas a un cosa y como lo defines siendo un múltiplo de 3 en "esperar que lo tuyo sea múltiplo de 3"? Gracias de antemano. - tzot
votó a favor de esto porque funciona en generadores (no len) y usa el módulo itertools generalmente más rápido. - Michael Dillon
Un ejemplo clásico de fantasía itertools
enfoque funcional que produce un lodo ilegible, en comparación con una implementación de Python pura simple e ingenua - wim
@wim Dado que esta respuesta comenzó como un fragmento de la documentación de Python, le sugiero que abra un problema en bugs.python.org . - tzot
@pedrosaurio si l==[1, 2, 3]
luego f(*l)
es equivalente a f(1, 2, 3)
. Vea esa pregunta y la documentación oficial. - tzot
277
Sé que esto es algo antiguo, pero nadie lo ha mencionado todavía. numpy.array_split
:
import numpy as np
lst = range(50)
np.array_split(lst, 5)
# [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])]
Respondido 14 Oct 19, 09:10
Esto le permite establecer el número total de fragmentos, no el número de elementos por fragmento. - FizxMike
puede hacer las matemáticas usted mismo. si tiene 10 elementos, puede agruparlos en trozos de 2, 5 elementos o cinco trozos de 2 elementos - Mío
+1 Esta es mi solución favorita, ya que divide la matriz en igualmente matrices de tamaño, mientras que otras soluciones no lo hacen (en todas las demás soluciones que miré, la última matriz puede ser arbitrariamente pequeña). - miniquark
@MiniQuark, pero ¿qué hace esto cuando la cantidad de bloques no es un factor del tamaño de la matriz original? - Baldrickk
@Baldrickk Si divide N elementos en K trozos, entonces los primeros N% K trozos tendrán N // K + 1 elementos, y el resto tendrá N // K elementos. Por ejemplo, si divide una matriz que contiene 108 elementos en 5 fragmentos, los primeros 108% 5 = 3 fragmentos contendrán 108 // 5 + 1 = 22 elementos, y el resto de los fragmentos tendrá 108 // 5 = 21 elementos. - miniquark
192
Me sorprende que nadie haya pensado en usar iter
's forma de dos argumentos:
from itertools import islice
def chunk(it, size):
it = iter(it)
return iter(lambda: tuple(islice(it, size)), ())
Demostració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 de forma perezosa. Devuelve tuplas en lugar de iteradores, pero creo que, no obstante, tiene cierta elegancia. Tampoco 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)
Demostració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 la izip_longest
-soluciones basadas en las anteriores siempre almohadillas. Hasta donde yo sé, no hay una receta de herramientas de iteración de una o dos líneas para una función que opcionalmente almohadillas. 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)
Demostració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 un relleno opcional.
Como Tomasz Gandor observado, los dos fragmentos de relleno se detendrán inesperadamente si encuentran una secuencia larga de valores de relleno. Aquí hay una variación final que soluciona ese problema de una manera razonable:
_no_padding = object()
def chunk(it, size, padval=_no_padding):
it = iter(it)
chunker = iter(lambda: tuple(islice(it, size)), ())
if padval == _no_padding:
yield from chunker
else:
for ch in chunker:
yield ch if len(ch) == size else ch + (padval,) * (size - len(ch))
Demostración:
>>> list(chunk([1, 2, (), (), 5], 2))
[(1, 2), ((), ()), (5,)]
>>> list(chunk([1, 2, None, None, 5], 2, None))
[(1, 2), (None, None), (5, None)]
respondido 17 nov., 18:01
Maravilloso, tu versión simple es mi favorita. A otros también se les ocurrió lo básico islice(it, size)
expresión y la incrusté (como yo había hecho) en una construcción de bucle. Sólo tú pensaste en la versión de dos argumentos de iter()
(Yo no lo sabía por completo), lo que lo hace súper elegante (y probablemente el más efectivo para el rendimiento). No tenía idea de que el primer argumento para iter
cambia a una función de 0 argumentos cuando se le da el centinela. Devuelve un iterador (pot. Infinito) de fragmentos, puede usar un iterador (pot. Infinito) como entrada, no tiene len()
y sin cortes de matriz. ¡Impresionante! - TomasH
Es por eso que leo las respuestas en lugar de escanear solo el par superior. El relleno opcional era un requisito en mi caso, y yo también aprendí sobre la forma de dos argumentos de iter. - Kerr
Yo voté a favor de esto, pero aún así, ¡no exageremos! En primer lugar, lambda puede ser malo (cierre lento it
iterador. En segundo lugar, y lo más importante: terminarás prematuramente si un padval
realmente existe en su iterable y debe procesarse. - tomasz gandor
@TomaszGandor, ¡acepto tu primer punto! Aunque tengo entendido que lambda no es más lento que una función ordinaria, por supuesto que tiene razón en que la llamada a la función y la búsqueda de cierre ralentizarán esto. No sé cuál sería el impacto relativo en el rendimiento de esto frente al izip_longest
enfoque, por ejemplo, sospecho que podría ser una compensación compleja. Pero ... no es el padval
problema compartido por cada respuesta aquí que ofrece un padval
¿parámetro? - remitente
@TomaszGandor, ¡justo! Pero no fue demasiado difícil crear una versión que solucione esto. (Además, tenga en cuenta que la primera versión, que utiliza ()
como el centinela, sí funciona correctamente. Esto es porque tuple(islice(it, size))
los rendimientos ()
cuando it
esta vacio.) - remitente
100
Aquí hay un generador que funciona 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]]
Respondido el 17 de Septiembre de 12 a las 22:09
55
def chunk(input, size):
return map(None, *([iter(input)] * size))
Respondido el 17 de Septiembre de 12 a las 22:09
map(None, iter)
iguales izip_longest(iter)
. - Tomas Ahle
@TomaszWysocki ¿Puedes explicar el *
frente a su tupla iterador? Posiblemente en su texto de respuesta, pero he visto que *
usado de esa manera en Python antes. ¡Gracias! - eljollysin
@theJollySin En este contexto, se denomina operador splat. Su uso se explica aquí - stackoverflow.com/questions/5917522/unzipping-and-the-operator. - rlms
Cerrar, pero el último fragmento no tiene ningún elemento para completarlo. Esto puede ser un defecto o no. Sin embargo, un patrón realmente genial. - usuario1969453
55
Simple pero elegante
l = range(1, 1000)
print [l[x:x+10] for x in xrange(0, len(l), 10)]
o si lo prefieres:
def chunks(l, n): return [l[x: x+n] for x in xrange(0, len(l), n)]
chunks(l, 10)
Respondido el 02 de junio de 20 a las 12:06
No copiarás una variable a semejanza de un número arábigo. En algunas fuentes, 1
y l
son indistinguibles. Como son 0
y O
. Y a veces incluso I
y 1
. - Alfa
@Alfe Fuentes defectuosas. La gente no debería usar tales fuentes. No para programar, no para cualquier cosa. - Jerry MEJOR
Las lambdas están diseñadas para usarse como funciones sin nombre. No tiene sentido usarlos así. Además, dificulta la depuración, ya que el rastreo informará "en "en lugar de" en trozos "en caso de error. Le deseo suerte para encontrar un problema si tiene un montón de estos :) - chris koston
debe ser 0 y no 1 dentro de xrange en print [l[x:x+10] for x in xrange(1, len(l), 10)]
- scottydelta
NOTA: Para usuarios de Python 3, use range
. - Decano cristiano
44
¿Cómo se divide una lista en partes iguales?
"Trozos de tamaño uniforme", para mí, implica que todos tienen la misma longitud, o salvo esa opción, en varianza mínima en longitud. Por ejemplo, 5 cestas para 21 artículos podrían tener los siguientes resultados:
>>> import statistics
>>> statistics.variance([5,5,5,5,1])
3.2
>>> statistics.variance([5,4,4,4,4])
0.19999999999999998
Una razón práctica para preferir el último resultado: si estuviera utilizando estas funciones para distribuir el trabajo, ha incorporado la perspectiva de que una probablemente termine mucho antes que las demás, por lo que se quedaría sentado sin hacer nada mientras los demás continuaban trabajando duro.
Crítica de otras respuestas aquí
Cuando escribí originalmente esta respuesta, ninguna de las otras respuestas tenía trozos de tamaño uniforme; todas dejan un trozo pequeño al final, por lo que no están bien equilibradas y tienen una variación de longitudes más alta de lo necesario.
Por ejemplo, la respuesta principal actual termina con:
[60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
[70, 71, 72, 73, 74]]
Otros, como list(grouper(3, range(7)))
y chunk(range(7), 3)
ambos regresan: [(0, 1, 2), (3, 4, 5), (6, None, None)]
. None
Son simplemente acolchados y, en mi opinión, bastante poco elegantes. NO están fragmentando uniformemente los iterables.
¿Por qué no podemos dividirlos mejor?
Solución de ciclo
Una solución equilibrada de alto nivel que utiliza itertools.cycle
, que es la forma en que podría hacerlo hoy. Aquí está la configuración:
from itertools import cycle
items = range(10, 75)
number_of_baskets = 10
Ahora necesitamos nuestras listas en las que rellenar los elementos:
baskets = [[] for _ in range(number_of_baskets)]
Finalmente, comprimimos los elementos que vamos a asignar junto con un ciclo de las cestas hasta que nos quedemos sin elementos, que semánticamente es exactamente lo que queremos:
for element, basket in zip(items, cycle(baskets)):
basket.append(element)
Aquí está el resultado:
>>> from pprint import pprint
>>> pprint(baskets)
[[10, 20, 30, 40, 50, 60, 70],
[11, 21, 31, 41, 51, 61, 71],
[12, 22, 32, 42, 52, 62, 72],
[13, 23, 33, 43, 53, 63, 73],
[14, 24, 34, 44, 54, 64, 74],
[15, 25, 35, 45, 55, 65],
[16, 26, 36, 46, 56, 66],
[17, 27, 37, 47, 57, 67],
[18, 28, 38, 48, 58, 68],
[19, 29, 39, 49, 59, 69]]
Para producir esta solución, escribimos una función y proporcionamos las anotaciones de tipo:
from itertools import cycle
from typing import List, Any
def cycle_baskets(items: List[Any], maxbaskets: int) -> List[List[Any]]:
baskets = [[] for _ in range(min(maxbaskets, len(items)))]
for item, basket in zip(items, cycle(baskets)):
basket.append(item)
return baskets
En lo anterior, tomamos nuestra lista de artículos y el número máximo de canastas. Creamos una lista de listas vacías, en las que agregar cada elemento, en un estilo round-robin.
Rebanadas
Otra solución elegante es usar rodajas, específicamente las que se usan con menos frecuencia. paso argumento a las rodajas. es decir:
start = 0
stop = None
step = number_of_baskets
first_basket = items[start:stop:step]
Esto es especialmente elegante en el sentido de que a los segmentos no les importa la longitud de los datos: el resultado, nuestra primera canasta, es solo lo largo que debe ser. Solo necesitaremos incrementar el punto de partida para cada canasta.
De hecho, esto podría ser de una sola línea, pero usaremos varias líneas para mejorar la legibilidad y evitar una línea de código demasiado larga:
from typing import List, Any
def slice_baskets(items: List[Any], maxbaskets: int) -> List[List[Any]]:
n_baskets = min(maxbaskets, len(items))
return [items[i::n_baskets] for i in range(n_baskets)]
Y islice
del módulo itertools proporcionará un enfoque de iteración perezosa, como el que se solicitó originalmente en la pregunta.
No espero que la mayoría de los casos de uso se beneficien mucho, ya que los datos originales ya están completamente materializados en una lista, pero para grandes conjuntos de datos, podría ahorrar casi la mitad del uso de memoria.
from itertools import islice
from typing import List, Any, Generator
def yield_islice_baskets(items: List[Any], maxbaskets: int) -> Generator[List[Any], None, None]:
n_baskets = min(maxbaskets, len(items))
for i in range(n_baskets):
yield islice(items, i, None, n_baskets)
Ver resultados con:
from pprint import pprint
items = list(range(10, 75))
pprint(cycle_baskets(items, 10))
pprint(slice_baskets(items, 10))
pprint([list(s) for s in yield_islice_baskets(items, 10)])
Soluciones anteriores actualizadas
Aquí hay otra solución equilibrada, adaptada de una función que he usado en producción en el pasado, que usa el operador de módulo:
def baskets_from(items, maxbaskets=25):
baskets = [[] for _ in range(maxbaskets)]
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 range(baskets):
yield [items[y_i] for y_i in range(x_i, item_count, baskets)]
Y finalmente, ya que veo que todas las funciones anteriores devuelven elementos en un orden contiguo (como se les dio):
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 range(baskets):
length = ceiling if x_i < stepdown else floor
yield [items.next() for _ in range(length)]
Salida
Para probarlos:
print(baskets_from(range(6), 8))
print(list(iter_baskets_from(range(6), 8)))
print(list(iter_baskets_contiguous(range(6), 8)))
print(baskets_from(range(22), 8))
print(list(iter_baskets_from(range(22), 8)))
print(list(iter_baskets_contiguous(range(22), 8)))
print(baskets_from('ABCDEFG', 3))
print(list(iter_baskets_from('ABCDEFG', 3)))
print(list(iter_baskets_contiguous('ABCDEFG', 3)))
print(baskets_from(range(26), 5))
print(list(iter_baskets_from(range(26), 5)))
print(list(iter_baskets_contiguous(range(26), 5)))
Que 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 fragmentos en los mismos patrones de longitud que los otros dos, pero los elementos están todos en orden y están divididos de la misma manera que uno puede dividir una lista de elementos discretos.
Respondido el 24 de enero de 21 a las 04:01
@senderle, el primero, list(grouper(3, xrange(7)))
, y el segundo, chunk(xrange(7), 3)
ambos regresan: [(0, 1, 2), (3, 4, 5), (6, None, None)]
. None
Son simplemente acolchados y, en mi opinión, bastante poco elegantes. NO están fragmentando uniformemente los iterables. ¡Gracias por tu voto! - Aaron Hall ♦
Usted plantea la pregunta (sin hacerlo explícitamente, así que lo hago ahora aquí) si fragmentos del mismo tamaño (excepto el último, si no es posible) o si un resultado equilibrado (lo mejor posible) es más a menudo lo que se necesitará. Supone que la solución equilibrada es preferir; esto podría ser cierto si lo que programa está cerca del mundo real (por ejemplo, un algoritmo de reparto de cartas para un juego de cartas simulado). En otros casos (como llenar las líneas con palabras), preferirá mantener las líneas lo más llenas posible. Así que realmente no puedo preferir uno sobre el otro; son solo para diferentes casos de uso. - Alfa
@ ChristopherBarrington-Leigh Buen punto, para DataFrames, probablemente debería usar cortes, ya que creo que los objetos DataFrame generalmente no se copian al cortar, por ejemplo import pandas as pd; [pd.DataFrame(np.arange(7))[i::3] for i in xrange(3)]
- Aaron Hall ♦
@AaronHall Vaya. Eliminé mi comentario porque dudé de mi crítica, pero fuiste rápido en el sorteo. ¡Gracias! De hecho, mi afirmación de que no funciona para marcos de datos es cierta. Si los elementos son un marco de datos, simplemente use los elementos de rendimiento [rango (x_i, item_count, cestas)] como última línea. Ofrecí una respuesta separada (otra más), en la que especificas el tamaño de grupo deseado (mínimo). - CPBL
43
Vi la respuesta más impresionante de Python en un duplicar 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)]
Puede crear n-tupla 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, puede reemplazar zip_longest
con zip
, de lo contrario el triplete (13, 14, None)
estaría perdido. Python 3 se usa arriba. Para Python 2, use izip_longest
.
Respondido el 21 de junio de 17 a las 14:06
eso es bueno si su lista y partes son cortas, ¿cómo podría adaptar esto para dividir su lista en partes de 1000? no vas a codificar zip (i, i, i, i, i, i, i, i, i, i ..... i = 1000) - Tom Smith
zip(i, i, i, ... i)
con argumentos "chunk_size" para zip () se puede escribir como zip(*[i]*chunk_size)
Si es una buena idea o no es discutible, por supuesto. - wilson f
La desventaja de esto es que si no está dividiendo uniformemente, dejará caer elementos, ya que zip se detiene en el iterable más corto, & izip_longest agregaría elementos predeterminados. - Aaron Hall ♦
zip_longest
debe usarse, como se hace en: stackoverflow.com/a/434411/1959808 - Ioannis Filippidis
La respuesta con range(1, 15)
ya le faltan elementos, porque hay 14 elementos en range(1, 15)
, no 15. - Ioannis Filippidis
39
Si conoce el tamaño de la lista:
def SplitList(mylist, chunk_size):
return [mylist[offs:offs+chunk_size] for offs in range(0, len(mylist), 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 el ú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).
Respondido 18 Abr '19, 12:04
Me entristece que esto esté tan enterrado. IterChunks funciona para todo y es la solución general y no tiene salvedades que yo sepa. - Jason Dunkelberger
21
Si tuviera un tamaño de fragmento de 3, por ejemplo, podría 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 es un número fijo que puedo escribir, por ejemplo, '3', y nunca cambiaría.
Respondido 19 Abr '11, 06:04
Esto no funciona si len (iterable)% 3! = 0. No se devolverá el último grupo (corto) de números. - Sherbang
20
EL herramientas la biblioteca tiene el partition
función para esto:
from toolz.itertoolz.core import partition
list(partition(2, [1, 2, 3, 4]))
[(1, 2), (3, 4)]
respondido 20 nov., 13:20
Esta parece la más simple de todas las sugerencias. Me pregunto si realmente puede ser cierto que uno tenga que usar una biblioteca de terceros para obtener dicha función de partición. Hubiera esperado que existiera algo equivalente con esa función de partición como un lenguaje incorporado. - Kasperd
puede hacer una partición con itertools. pero me gusta la biblioteca toolz. es una biblioteca inspirada en clojure para trabajar en colecciones con un estilo funcional. no obtienes inmutabilidad pero obtienes un pequeño vocabulario para trabajar en colecciones simples. Como ventaja, cytoolz está escrito en cython y obtiene un buen aumento de rendimiento. github.com/pytoolz/cytoolz matthewrocklin.com/blog/work/2014/05/01/Introducing-CyToolz - zach
El enlace del comentario de zach funciona si omite la barra al final: matthewrocklin.com/blog/work/2014/05/01/Introducing-CyToolz - con
20
[AA[i:i+SS] for i in range(len(AA))[::SS]]
Donde AA es matriz, SS es tamaño de fragmento. 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
Respondido el 16 de diciembre de 15 a las 21:12
es el mejor y más sencillo. - F.Tamy
breve y simple. simplicidad sobre complejidad. - hombre oscuro
¡Muy útil! ¡Gracias! - Mateus da Silva Teixeira
20
Con Expresiones de asignación en Python 3.8 se vuelve bastante agradable:
import itertools
def batch(iterable, size):
it = iter(iterable)
while item := list(itertools.islice(it, size)):
yield item
Esto funciona en un iterable arbitrario, no solo en una lista.
>>> import pprint
>>> pprint.pprint(list(batch(range(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]]
Respondido el 10 de diciembre de 19 a las 11:12
Ahora bien, esta es una nueva y valiosa respuesta a esta pregunta. De hecho, me gusta bastante esto. Soy escéptico con las expresiones de asignación, pero cuando funcionan, funcionan. - juanpa.arrivillaga
17
Me gusta mucho la versión del documento de Python propuesta por tzot y JFSebastian, pero tiene dos defectos:
- no es muy explícito
- Por lo general, no quiero un valor de relleno en el último fragmento
Estoy usando mucho este 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 de fragmentos perezosos:
from itertools import chain, islice
def chunks(n, iterable):
iterable = iter(iterable)
while True:
yield chain([next(iterable)], islice(iterable, n-1))
respondido 09 nov., 13:08
¿Cuál es la condición de ruptura para el while True
¿círculo? - andrea
@wjandrea: El StopIteration
criado cuando el tuple
está vacío y iterable.next()
se ejecuta. Sin embargo, no funciona correctamente en Python moderno, donde la salida de un generador debe hacerse con return
, no levantando StopIteration
. Una try/except StopIteration: return
alrededor de todo el bucle (y cambiando iterable.next()
a next(iterable)
para compatibilidad de versiones cruzadas) corrige esto con una sobrecarga mínima al menos. - ShadowRanger
16
Tenía curiosidad sobre 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
Respondido el 07 de enero de 18 a las 08:01
evaluación comparativa utilizando time
biblioteca no es una gran idea cuando tenemos timeit
módulo - Azat Ibrakov
14
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]]
Respondido 31 ago 16, 18:08
13
También puedes usar get_chunks
función de utilspie
biblioteca 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]]
Puede instalar utilspie
a través de pip:
sudo pip install utilspie
Descargo de responsabilidad: soy el creador de útil bibliotecas.
Respondido el 27 de enero de 17 a las 23:01
12
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 alienígena masiva, 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
respondido 03 nov., 15:23
11
Aquí hay una lista de enfoques adicionales:
Dado que los
import itertools as it
import collections as ct
import more_itertools as mit
iterable = range(11)
n = 3
Code
La biblioteca estándar
list(it.zip_longest(*[iter(iterable)] * n))
# [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, None)]
d = {}
for i, x in enumerate(iterable):
d.setdefault(i//n, []).append(x)
list(d.values())
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]
dd = ct.defaultdict(list)
for i, x in enumerate(iterable):
dd[i//n].append(x)
list(dd.values())
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]
list(mit.chunked(iterable, n))
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]
list(mit.sliced(iterable, n))
# [range(0, 3), range(3, 6), range(6, 9), range(9, 11)]
list(mit.grouper(n, iterable))
# [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, None)]
list(mit.windowed(iterable, len(iterable)//n, step=n))
# [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, None)]
Referencias
zip_longest
(Publicación relacionada, Publicación relacionada)setdefault
(los resultados ordenados requieren Python 3.6+)collections.defaultdict
(los resultados ordenados requieren Python 3.6+)more_itertools.chunked
(relacionados publicados)more_itertools.sliced
more_itertools.grouper
(Publicación relacionada)more_itertools.windowed
(Véase tambiénstagger
,zip_offset
)
+ Una biblioteca de terceros que implementa recetas de herramientas y más. > pip install more_itertools
Respondido 26 ago 18, 02:08
9
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
Respondido 28 Feb 15, 20:02
(2016 de septiembre de 12) Esta respuesta es la más independiente del idioma y la más fácil de leer. - Adams
8
je, versión de una 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]]
respondido 23 nov., 08:12
Por favor, use "def chunk" en lugar de "chunk = lambda". Funciona igual. Una línea. Mismas características. MUCHO más fácil de leer y comprender para el n00bz. - S. Lot
@ S.Lott: no si el n00bz proviene del esquema: P, esto no es un problema real. ¡incluso hay una palabra clave para google! ¿Qué otras características muestran que evitamos por el bien del n00bz? Supongo que el rendimiento no es lo suficientemente imperativo como para ser compatible con n00b. - Janus Troelsen
El objeto de función resultante de def chunk
en lugar de chunk=lambda
tiene .__ nombre__ atributo 'fragmento' en lugar de ' '. El nombre específico es más útil en los rastreos. - Terry Jan Reedy
@Alfe: No estoy seguro de si podría llamarse una diferencia semántica principal, pero si hay un nombre útil en un rastreo en lugar de <lamba>
o no es, al menos, una diferencia notable. - martillo
Después de probar el rendimiento de un montón de ellos, ¡ESTO es genial! - Patel soleado
8
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
respondido 24 nov., 08:16
8
Sin llamar a len () 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))
contestado el 16 de mayo de 16 a las 07:05
No hay razón para evitar len()
en listas grandes; es una operación de tiempo constante. - Tomás Wouters
7
Ver esta referencia
>>> orange = range(1, 1001)
>>> otuples = list( zip(*[iter(orange)]*10))
>>> print(otuples)
[(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), ... (991, 992, 993, 994, 995, 996, 997, 998, 999, 1000)]
>>> olist = [list(i) for i in otuples]
>>> print(olist)
[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ..., [991, 992, 993, 994, 995, 996, 997, 998, 999, 1000]]
>>>
Python3
respondido 14 nov., 14:09
Agradable, pero deja caer elementos al final si el tamaño no coincide con números enteros de trozos, p. Ej. zip(*[iter(range(7))]*3)
solo devoluciones [(0, 1, 2), (3, 4, 5)]
y olvida el 6
desde la entrada. - Alfa
OP escribió: 'Tengo una lista de longitud arbitraria, y necesito dividirla en trozos de igual tamaño y operar en ella'. Tal vez me pierda algo, pero cómo obtener 'trozos de igual tamaño' de la lista de longitud arbitraria sin eliminar un trozo que sea más corto que 'igual tamaño' - Aivar Paalberg
7
Dado que todos aquí están hablando de iteradores. boltons
tiene un método perfecto para eso, llamado iterutils.chunked_iter
.
from boltons import iterutils
list(iterutils.chunked_iter(list(range(50)), 11))
Salida:
[[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]]
Pero si no quiere tener piedad de la memoria, puede usar el método antiguo y almacenar el list
en primer lugar con iterutils.chunked
.
respondido 03 nov., 16:19
¡¡Y este realmente funciona independientemente del orden en que se mire a los subiteradores !! - Pedro Gerdes
6
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]]'
Respondido 13 Feb 12, 04:02
Si bien esto puede no parecer tan corto o tan bonito como muchas de las respuestas basadas en itertools, esta realmente funciona si desea imprimir la segunda sublista antes de acceder a la primera, es decir, puede configurar i0 = next (g2); i1 = siguiente (g2); y use i1 antes de usar i0 y no se rompe !! - Pedro Gerdes
6
Considera usar matplotlib.cbook piezas
por ejemplo:
import matplotlib.cbook as cbook
segments = cbook.pieces(np.arange(20), 3)
for s in segments:
print s
respondido 08 mar '12, 18:03
Parece que accidentalmente creó dos cuentas. Usted puede contacta al equipo para fusionarlos, lo que le permitirá recuperar los privilegios de edición directa de sus contribuciones. - Georgy
6
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 )]
Respondido 16 Jul 15, 00:07
¿Puede explicar más su respuesta por favor? - Zulu
Trabajando desde atrás: (len (a) + CHUNK -1) / CHUNK Le da la cantidad de trozos con los que terminará. Luego, para cada fragmento en el índice i, estamos generando un subarreglo del arreglo original como este: a [i * CHUNK: (i + 1) * CHUNK] donde, i * CHUNK es el índice del primer elemento a poner en el subarreglo, y, (i + 1) * CHUNK es 1 después del último elemento para poner en el subarreglo. Esta solución utiliza la comprensión de listas, por lo que podría ser más rápida para arreglos grandes. - AdvilUser
No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas python list split chunks or haz tu propia pregunta.
Antes de publicar una nueva respuesta, considere que ya hay más de 60 respuestas para esta pregunta. Por favor, asegúrese de que su respuesta aporte información que no se encuentre entre las respuestas existentes. - janniks