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

2269

Tengo una lista de longitud arbitraria, y necesito dividirla en trozos de igual tamaño y operarla. Hay algunas formas obvias de hacer esto, como mantener un contador y dos listas, y cuando se llene la segunda lista, 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 para listas de cualquier longitud, por ejemplo, usando generadores.

Estaba buscando algo útil itertoolspero no pude encontrar nada obviamente útil. Sin embargo, podría haberlo perdido.

Pregunta relacionada: ¿Cuál es la forma más "pitónica" de iterar sobre una lista en fragmentos?

jespern
fuente
1
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 contribuya con información que no se encuentre entre las respuestas existentes.
Janniks
Para los usuarios que desean evitar una porción final arbitrariamente pequeña, revise la división de una lista en N partes de aproximadamente la misma longitud
wim

Respuestas:

3153

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 usa Python 2, debe usar en xrange()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 la lista 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)]
Ned Batchelder
fuente
72
¿Qué sucede si no podemos decir la longitud de la lista? Pruebe esto en itertools.repeat ([1, 2, 3]), por ejemplo
jespern el
47
Esa es una extensión interesante de la pregunta, pero la pregunta original claramente se refería a operar en una lista.
Ned Batchelder
33
esta función debe estar en la maldita biblioteca estándar
dgan
66
@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án claramente de acuerdo con el último fragmento de tamaño diferente. ¿Quizás la frase en inglés es imprecisa?
Ned Batchelder
8
No nombre sus variables l, se ve exactamente como 1 y es confuso. La gente está copiando su código y piensa que está bien.
Yasen
555

Si quieres algo super simple:

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

Usar en xrange()lugar de range()en el caso de Python 2.x

oremj
fuente
66
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 rango (0, len (x), y) ] ¡Me encanta este método de comprensión de listas!
JP
44
después del retorno debe haber [, no (
alwbtc
2
"Super simple" significa no tener que depurar bucles infinitos, felicitaciones por el max().
Bob Stein
No hay nada simple sobre esta solución
mit
1
@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 menos de 1 en 1.
Bob Stein
295

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, según lo sugerido 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, estaba funcionando de nuevo.

Estas soluciones funcionan porque [iter(iterable)]*n(o el equivalente en la versión anterior) crea un iterador, repetido nveces en la lista. izip_longestluego realiza de manera efectiva un round robin de "cada" iterador; Debido a que este es el mismo iterador, cada una de esas llamadas avanza, lo que da como resultado que cada zip-roundrobin genere una tupla de nelementos.

tzot
fuente
@ninjagecko: list(grouper(3, range(10)))regresa [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, None, None)], y todas las tuplas son de longitud 3. Explique su comentario porque no puedo entenderlo; ¿cómo se llama una cosa y cómo se define como un múltiplo de 3 en "esperar que su cosa sea un múltiplo de 3"? Gracias de antemano.
tzot
14
votó esto porque funciona en generadores (sin len) y utiliza el módulo de herramientas de iterto generalmente más rápido.
Michael Dillon
88
Un ejemplo clásico de itertoolsenfoque funcional sofisticado que
produce
15
@wim Dado que esta respuesta comenzó como un fragmento de la documentación de Python, te sugiero que abras un problema en bugs.python.org .
tzot
1
@pedrosaurio si l==[1, 2, 3]entonces f(*l)es equivalente a f(1, 2, 3). Ver esa pregunta y la documentación oficial .
tzot
227

Sé que esto es un poco viejo, pero nadie ha mencionado aún 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])]
Moj
fuente
12
Esto le permite establecer el número total de fragmentos, no el número de elementos por fragmento.
FizxMike
66
Puedes hacer los cálculos tú mismo. si tiene 10 elementos, puede agruparlos en 2, 5 elementos o cinco elementos de 2 elementos
Moj
24
+1 Esta es mi solución favorita, ya que divide la matriz en matrices de tamaño uniforme , 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 original de la matriz?
Baldrickk
1
@Baldrickk Si divide N elementos en K fragmentos, entonces los primeros N% K fragmentos 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, el primer 108% 5 = 3 fragmentos contendrá 108 // 5 + 1 = 22 elementos, y el resto de los fragmentos tendrá 108 // 5 = 21 elementos.
MiniQuark
147

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

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 una salida perezosa. Devuelve tuplas en lugar de iteradores, pero creo que tiene una cierta elegancia. Tampoco rellena; Si desea relleno, una variación simple 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_longestsoluciones basadas en lo anterior, lo anterior siempre es perfecto. Hasta donde sé, 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 chunker más corto propuesto que ofrece relleno opcional.

Como observó Tomasz Gandor , los dos fragmentos de relleno se detendrán inesperadamente si encuentran una larga secuencia de valores de relleno. Aquí hay una variación final que resuelve ese problema de 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))

Manifestació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)]
senderle
fuente
77
Maravilloso, tu versión simple es mi favorita. A otros también se les ocurrió la islice(it, size)expresión básica y la incrustaron (como lo había hecho yo) en una construcción de bucle. Solo que pensaste en la versión de dos argumentos de iter()(que desconocía por completo), lo que la hace súper elegante (y probablemente la más efectiva para el rendimiento). No tenía idea de que el primer argumento para itercambiar a una función de argumento 0 cuando se le dio el centinela. Devuelve un iterador (pot. Infinito) de trozos, puede usar un iterador (pot. Infinito) como entrada, no tiene ni len()ningún segmento de matriz. ¡Increíble!
ThomasH
1
Es por eso que leí las respuestas en lugar de escanear solo las dos primeras. El relleno opcional era un requisito en mi caso, y yo también aprendí sobre la forma de iter de dos argumentos.
Kerr
Voté esto, pero aún así, ¡no lo exageremos! En primer lugar, lambda puede ser malo (cierre lento sobre el ititerador. En segundo lugar, y lo más importante: terminará prematuramente si padvalrealmente existe un fragmento de su iterable, y debe procesarse.
Tomasz Gandor
@TomaszGandor, ¡tomo tu primer punto! Aunque entiendo que lambda no es más lenta que una función ordinaria, por supuesto tiene razón en que la llamada a la función y la búsqueda de cierre retrasarán esto. No sé cuál sería el impacto relativo de rendimiento de esto en comparación con el izip_longestenfoque, por ejemplo, sospecho que podría ser una compensación compleja. Pero ... ¿no es el padvalproblema compartido por cada respuesta aquí que ofrece un padvalparámetro?
senderle
1
@TomaszGandor, ¡justo! Pero no fue demasiado difícil crear una versión que solucione esto. (También, nota que la primera versión, que utiliza ()como el centinela, que hace el trabajo correctamente Esto es debido. tuple(islice(it, size))Rendimientos ()cuando itestá vacío.)
senderle
93

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]]
Markus Jarderot
fuente
52
def chunk(input, size):
    return map(None, *([iter(input)] * size))
Tomasz Wysocki
fuente
map(None, iter)es igual izip_longest(iter).
Thomas Ahle
1
@TomaszWysocki ¿Puedes explicar la *tupla de tu iterador frente a ti? Posiblemente en su texto de respuesta, pero he notado que se *usaba de esa manera en Python antes. ¡Gracias!
theJollySin
1
@theJollySin En este contexto, se llama operador splat. Su uso se explica aquí: stackoverflow.com/questions/5917522/unzipping-and-the-operator .
rlms
2
Cerrar pero el último fragmento no tiene elementos para completarlo. Esto puede o no ser un defecto. Patrón realmente genial sin embargo.
49

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)
lebenf
fuente
18
No deberás doblar una variable a semejanza de un número árabe. En algunas fuentes, 1y lson indistinguibles. Como son 0y O. Y a veces incluso Iy 1.
Alfe
14
@Alfe Fuentes defectuosas. La gente no debería usar esas fuentes. No para programar, no para nada .
Jerry B
17
Las lambdas están destinadas a ser utilizadas como funciones sin nombre. No tiene sentido usarlos así. Además, hace que la depuración sea más difícil ya que el rastreo informará "en <lambda>" en lugar de "en fragmentos" en caso de error. Le deseo suerte para encontrar un problema si tiene un montón de estos :)
Chris Koston
1
debería ser 0 y no 1 dentro de xrange enprint [l[x:x+10] for x in xrange(1, len(l), 10)]
scottydelta
NOTA: para los usuarios de Python 3 range.
Christian Dean el
40

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 utilizando estas funciones para distribuir el trabajo, ha incorporado la posibilidad de que uno termine mucho antes que los demás, por lo que se quedaría sin hacer nada mientras los demás 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]]

¡Solo odio a esa runa al final!

Otros, como list(grouper(3, xrange(7))), y chunk(xrange(7), 3)tanto la rentabilidad: [(0, 1, 2), (3, 4, 5), (6, None, None)]. Los None's son solo relleno, y en mi opinión, bastante poco elegantes. NO están fragmentando uniformemente los iterables.

¿Por qué no podemos dividir esto mejor?

Mis soluciones

Aquí hay una solución equilibrada, adaptada de una función que he usado en producción (Nota en Python 3 para reemplazar xrange a 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 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 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 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]]

Tenga en cuenta 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 manera tan uniforme como uno puede dividir una lista de elementos discretos.

Aaron Hall
fuente
Usted dice que ninguno de los anteriores proporciona trozos de tamaño uniforme. Pero este sí, como este .
senderle
1
@senderle, el primero, list(grouper(3, xrange(7)))y el segundo, chunk(xrange(7), 3)ambos vuelven: [(0, 1, 2), (3, 4, 5), (6, None, None)]. Los None's son solo relleno, y en mi opinión, bastante poco elegantes. NO están fragmentando uniformemente los iterables. Gracias por tu voto!
Aaron Hall
44
Planteas la cuestión (sin hacerlo explícitamente, así que lo hago ahora aquí) si lo que se necesitará con mayor frecuencia es un fragmento del mismo tamaño (excepto el último, si no es posible) o si un resultado equilibrado (tan bueno como sea posible). Asumes que la solución equilibrada es preferible; 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 líneas con palabras), preferiría mantener las líneas lo más completas posible. Así que realmente no puedo preferir uno sobre el otro; son solo para diferentes casos de uso.
Alfe
@ ChristopherBarrington-Leigh Buen punto, para DataFrames, probablemente deberías usar cortes, ya que creo que los objetos DataFrame no suelen copiarse en cortes, por ejemploimport pandas as pd; [pd.DataFrame(np.arange(7))[i::3] for i in xrange(3)]
Aaron Hall
1
@AaronHall Vaya. Eliminé mi comentario porque adiviné mi crítica, pero fuiste rápido en el sorteo. ¡Gracias! De hecho, mi afirmación de que no funciona para los marcos de datos es cierta. Si los ítems son un marco de datos, solo use ítems de rendimiento [rango (x_i, recuento de ítems, canastas)] como la última línea. Ofrecí una respuesta separada (otra más), en la que especificas el tamaño de grupo deseado (mínimo).
CPBL
38

Vi la respuesta más impresionante de Python-ish en un duplicado 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-tuplas 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 se divide por igual, entonces se puede sustituir zip_longestcon zip, de lo contrario el triplete (13, 14, None)se perdería. Python 3 se usa arriba. Para Python 2, use izip_longest.

Noich
fuente
eso es bueno si su lista y fragmentos son cortos, ¿cómo podría adaptar esto para dividir su lista en fragmentos de 1000? no vas a codificar zip (i, i, i, i, i, i, i, i, i, i ..... i = 1000)
Tom Smith
99
zip(i, i, i, ... i)con argumentos "chunk_size" para zip () se puede escribir como zip(*[i]*chunk_size)si eso es una buena idea o no es discutible, por supuesto.
Wilson F
1
La desventaja de esto es que si no está dividiendo de manera uniforme, soltará elementos, ya que zip se detiene en el iterativo más corto, y izip_longest agregaría elementos predeterminados.
Aaron Hall
zip_longestdebe usarse, como se hace en: stackoverflow.com/a/434411/1959808
Ioannis Filippidis
La respuesta con range(1, 15)ya falta elementos, porque hay 14 elementos en range(1, 15), no 15.
Ioannis Filippidis
35

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 bella 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).

atzz
fuente
Estoy triste de que esto esté enterrado tan lejos. El IterChunks funciona para todo y es la solución general y no tiene advertencias que yo sepa.
Jason Dunkelberger
18

La biblioteca toolz tiene la partitionfunción para esto:

from toolz.itertoolz.core import partition

list(partition(2, [1, 2, 3, 4]))
[(1, 2), (3, 4)]
zach
fuente
Esta parece la más simple de todas las sugerencias. Me pregunto si realmente puede ser cierto que uno tiene que usar una biblioteca de terceros para obtener dicha función de partición. Hubiera esperado que algo equivalente con esa función de partición existiera como un lenguaje incorporado.
Kasperd
1
Puedes 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 un plus, 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 diagonal final: matthewrocklin.com/blog/work/2014/05/01/Introducing-CyToolz
mit
17

Si tenía 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/

Lo usaría cuando mi tamaño de fragmento es un número fijo que puedo escribir, por ejemplo, '3', y nunca cambiaría.

ninjagecko
fuente
11
Esto no funciona si len (iterable)% 3! = 0. El último grupo (corto) de números no se devolverá.
sherbang
16

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

  • no es muy explícito
  • Por lo general, no quiero un valor de relleno en el último fragmento

Estoy usando este 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 de trozos perezosos:

from itertools import chain, islice

def chunks(n, iterable):
   iterable = iter(iterable)
   while True:
       yield chain([next(iterable)], islice(iterable, n-1))
nikipore
fuente
¿Cuál es la condición de ruptura para el while Truebucle?
wjandrea
@wjandrea: se StopIterationeleva cuando tupleestá vacío y iterable.next()se ejecuta. Sin embargo, no funciona correctamente en Python moderno, donde se debe salir de un generador return, no subir StopIteration. A try/except StopIteration: returnalrededor de todo el ciclo (y cambiando iterable.next()a next(iterable)compatibilidad con versiones cruzadas) corrige esto con una sobrecarga mínima al menos.
ShadowRanger
15
[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
Riaz Rizvi
fuente
2
Es lo mejor y lo más simple.
F.Tamy
2
Corto y simple. simplicidad sobre complejidad.
dkrynicki
15

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
Alex T
fuente
3
el benchmarking usando la timebiblioteca no es una gran idea cuando tenemos un timeitmódulo
Azat Ibrakov
13

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]]
Art B
fuente
12

También puede usar la get_chunksfunción de utilspiebiblioteca 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 a utilspietravés de pip:

sudo pip install utilspie

Descargo de responsabilidad: soy el creador de la biblioteca utilspie .

Moinuddin Quadri
fuente
11

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
mazieres
fuente
9

Con las 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 iterativo 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]]
nirvana-msu
fuente
1
Ahora esta es una nueva respuesta digna a esta pregunta. De hecho, me gusta bastante esto. Soy escéptico de las expresiones de asignación, pero cuando funcionan, funcionan.
juanpa.arrivillaga
7

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]]
slav0nic
fuente
36
Por favor, use "def chunk" en lugar de "chunk = lambda". Funciona igual Una línea. Las mismas características. MUCHO más fácil para el n00bz de leer y entender.
S.Lott
44
@ S.Lott: no si los n00bz provienen 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 de n00bz? Supongo que el rendimiento no es lo suficientemente imperativo / similar a c para ser amigable con n00b.
Janus Troelsen
16
El objeto de función resultante de en def chunklugar de chunk=lambdatiene .__ name__ atributo 'chunk' en lugar de '<lambda>'. El nombre específico es más útil en las trazas.
Terry Jan Reedy
1
@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.
Martineau
1
Después de probar un montón de ellos para el rendimiento, ¡ESTO es genial!
Sunny Patel
7
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
Corey Goldberg
fuente
7

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
Ranaivo
fuente
(12 de septiembre de 2016) Esta respuesta es la más independiente del idioma y la más fácil de leer.
D Adams
7

Sin llamar a len (), lo cual 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))
Marte
fuente
16
No hay razón para evitar len()en listas grandes; Es una operación de tiempo constante.
Thomas Wouters
7

Aquí hay una lista de enfoques adicionales:

Dado

import itertools as it
import collections as ct

import more_itertools as mit


iterable = range(11)
n = 3

Código

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]]

more_itertools+

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

+ Una biblioteca de terceros que implementa recetas de itertools y más.> pip install more_itertools

pylang
fuente
6

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

macm
fuente
3
Agradable, pero elimina elementos al final si el tamaño no coincide con números enteros de fragmentos, por ejemplo, zip(*[iter(range(7))]*3)solo devuelve [(0, 1, 2), (3, 4, 5)]y olvida el 6de la entrada.
Alfe
6

Ya que todos aquí están hablando de iteradores. boltonstiene 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 desea ser misericordioso con la memoria, puede usar el método antiguo y almacenarlo completo listen primer lugar con iterutils.chunked.

vishes_shell
fuente
¡Y este realmente funciona independientemente del orden en que se mire a los subiteradores!
Peter Gerdes
6

Una solución mas

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]
>>> 
Анатолий Панин
fuente
5
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]]'
robert king
fuente
1
Si bien esto puede no parecer tan corto o 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 establecer i0 = next (g2); i1 = siguiente (g2); y use i1 antes de usar i0 y no se rompe !!
Peter Gerdes
5

Considere usar piezas matplotlib.cbook

por ejemplo:

import matplotlib.cbook as cbook
segments = cbook.pieces(np.arange(20), 3)
for s in segments:
     print s
Schwater
fuente
Parece que accidentalmente creaste dos cuentas. Puede ponerse en contacto con el equipo para fusionarlos, lo que le permitirá recuperar los privilegios de edición directa de sus contribuciones.
Georgy