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


Tengo una lista de longitud arbitraria, y necesito dividirla en trozos de igual tamaño y operar en ella. Hay algunas maneras obvias de hacerlo, como mantener un contador y dos listas, y cuando la segunda lista se llena, agréguela a la primera lista y vacíe la segunda lista para la próxima ronda de datos, pero esto es potencialmente extremadamente costoso.

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

Estaba buscando algo útil en itertools pero no pude encontrar nada obviamente útil. Podría haberlo perdido, sin embargo.

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


1578
2017-11-23 12:15


origen


Respuestas:


Aquí hay un generador que produce los fragmentos que desea:

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 usa Python 2, debe usar xrange() en 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 usar la comprensión de listas en lugar de escribir una función. Python 3:

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

Versión Python 2:

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

2113
2017-11-23 12:33



Si quieres algo súper simple:

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

481
2017-11-17 20:17



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, como lo sugiere J.F.Sebastian:

#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 nuevamente.

Estas soluciones funcionan porque [iter(iterable)]*n (o el equivalente en la versión anterior) crea uno 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, es avanzado por cada llamada, lo que resulta en que cada zip-roundrobin genere una tupla de n artículos.


251
2017-11-23 15:48



Sé que esto es algo 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])]

94
2018-06-05 08:54



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

79
2017-11-23 12:41



Me sorprende que nadie haya pensado en usar iteres 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 salida perezosamente. Devuelve tuplas en lugar de iteradores, pero creo que tiene cierta elegancia, no obstante. Tampoco se rellena; si quieres relleno, bastará con una simple variación de lo anterior:

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

Como el izip_longestsoluciones basadas en la base siempre almohadillas. Por lo que sé, no hay una o dos líneas de recetas 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)

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.


65
2018-02-26 15:02



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

48
2018-06-26 19:10



Simple pero elegante

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

o si lo prefiere:

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

39
2017-07-12 07:58