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

488

Tengo un script de Python que toma como entrada una lista de enteros, que necesito para trabajar con cuatro enteros a la vez. Desafortunadamente, no tengo control de la entrada, o me la pasaría como una lista de tuplas de cuatro elementos. Actualmente, estoy iterando sobre esto de esta manera:

for i in xrange(0, len(ints), 4):
    # dummy op for example code
    foo += ints[i] * ints[i + 1] + ints[i + 2] * ints[i + 3]

Sin embargo, se parece mucho a "C-think", lo que me hace sospechar que hay una forma más pitónica de lidiar con esta situación. La lista se descarta después de iterar, por lo que no es necesario conservarla. ¿Quizás algo como esto sería mejor?

while ints:
    foo += ints[0] * ints[1] + ints[2] * ints[3]
    ints[0:4] = []

Sin embargo, todavía no se "siente" bien. : - /

Pregunta relacionada: ¿Cómo se divide una lista en partes iguales en Python?

Ben Blank
fuente
3
Su código no funciona si el tamaño de la lista no es múltiplo de cuatro.
Pedro Henriques
55
Estoy extendiendo () la lista para que su longitud sea un múltiplo de cuatro antes de llegar tan lejos.
Ben Blank
44
@ ΤΖΩΤΖΙΟΥ: las preguntas son muy similares, pero no del todo duplicadas. Se "divide en cualquier número de fragmentos de tamaño N" frente a "se divide en N fragmentos de cualquier tamaño". :-)
Ben Blank
1
Posible duplicado del generador
Cristian Ciupitu

Respuestas:

340

Modificado de la sección de recetas de los documentos de itertools de Python :

from itertools import zip_longest

def grouper(iterable, n, fillvalue=None):
    args = [iter(iterable)] * n
    return zip_longest(*args, fillvalue=fillvalue)

Ejemplo
en pseudocódigo para mantener el ejemplo conciso.

grouper('ABCDEFG', 3, 'x') --> 'ABC' 'DEF' 'Gxx'

Nota: en Python 2 use en izip_longestlugar dezip_longest .

Loco
fuente
67
Finalmente tuve la oportunidad de jugar con esto en una sesión de Python. Para aquellos que están tan confundidos como yo, esto está alimentando al mismo iterador para izip_longest varias veces, lo que hace que consuma valores sucesivos de la misma secuencia en lugar de valores rayados de secuencias separadas. ¡Me encanta!
Ben Blank
66
¿Cuál es la mejor manera de filtrar el valor de relleno? ([ítem por ítem en ítems si el ítem no tiene valor de relleno] para ítems en mero (iterable))?
gotgenes
14
Sospecho que el rendimiento de esta receta de mero para trozos de 256k será muy pobre, porque izip_longestse alimentará con 256k argumentos.
anatoly techtonik
13
En varios lugares, los comentaristas dicen "cuando finalmente descubrí cómo funcionó esto ..." Tal vez se requiera un poco de explicación. Particularmente el aspecto de la lista de iteradores.
LondonRob
66
¿Hay alguna manera de usar esto pero sin Nonellenar el último fragmento?
CMCDragonkai
420
def chunker(seq, size):
    return (seq[pos:pos + size] for pos in range(0, len(seq), size))
# (in python 2 use xrange() instead of range() to avoid allocating a list)

Simple. Fácil. Rápido. Funciona con cualquier secuencia:

text = "I am a very, very helpful text"

for group in chunker(text, 7):
   print repr(group),
# 'I am a ' 'very, v' 'ery hel' 'pful te' 'xt'

print '|'.join(chunker(text, 10))
# I am a ver|y, very he|lpful text

animals = ['cat', 'dog', 'rabbit', 'duck', 'bird', 'cow', 'gnu', 'fish']

for group in chunker(animals, 3):
    print group
# ['cat', 'dog', 'rabbit']
# ['duck', 'bird', 'cow']
# ['gnu', 'fish']
nosklo
fuente
16
La versión de @Carlos Crasborn funciona para cualquier iterable (no solo secuencias como el código anterior); es conciso y probablemente igual de rápido o incluso más rápido. Aunque puede ser un poco oscuro (poco claro) para las personas que no están familiarizadas con el itertoolsmódulo.
jfs el
1
Convenido. Esta es la forma más genérica y pitónica. Claro y conciso. (y funciona en el motor de aplicaciones)
Matt Williamson
3
Tenga en cuenta que chunkerdevuelve a generator. Reemplace el retorno a: return [...]para obtener una lista.
Dror
11
En lugar de escribir un edificio de función y luego regresar un generador, también se podría escribir un generador directamente, mediante yield: for pos in xrange(0, len(seq), size): yield seq[pos:pos + size]. No estoy seguro si internamente esto se manejaría de manera diferente en cualquier aspecto relevante, pero podría ser incluso un poco más claro.
Alfe
3
Tenga en cuenta que esto solo funciona para secuencias que admiten el acceso a elementos por índice y no funcionará para iteradores genéricos, ya que es posible que no admitan el __getitem__método.
apollov
135

Soy un fan de

chunk_size= 4
for i in range(0, len(ints), chunk_size):
    chunk = ints[i:i+chunk_size]
    # process chunk of size <= chunk_size
S.Lott
fuente
¿Cómo se comporta si len (ints) no es un múltiplo de chunkSize?
PlsWork
3
@AnnaVopureta chunktendrá 1, 2 o 3 elementos para el último lote de elementos. Vea esta pregunta sobre por qué los índices de corte pueden estar fuera de los límites .
Boris
22
import itertools
def chunks(iterable,size):
    it = iter(iterable)
    chunk = tuple(itertools.islice(it,size))
    while chunk:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

# though this will throw ValueError if the length of ints
# isn't a multiple of four:
for x1,x2,x3,x4 in chunks(ints,4):
    foo += x1 + x2 + x3 + x4

for chunk in chunks(ints,4):
    foo += sum(chunk)

De otra manera:

import itertools
def chunks2(iterable,size,filler=None):
    it = itertools.chain(iterable,itertools.repeat(filler,size-1))
    chunk = tuple(itertools.islice(it,size))
    while len(chunk) == size:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

# x2, x3 and x4 could get the value 0 if the length is not
# a multiple of 4.
for x1,x2,x3,x4 in chunks2(ints,4,0):
    foo += x1 + x2 + x3 + x4
Markus Jarderot
fuente
2
+1 por usar generadores, parece la solución más "pitónica" de todas las soluciones sugeridas
Sergey Golovchenko
77
Es bastante largo y torpe para algo tan fácil, que no es muy pitónico. Prefiero la versión de S. Lott
zenazn
44
@zenazn: esto funcionará en instancias de generador, el corte no lo hará
Janus Troelsen
Además de funcionar correctamente con generadores y otros iteradores que no se pueden cortar, la primera solución tampoco requiere un valor de "relleno" si el fragmento final es menor que size, lo que a veces es deseable.
dano
1
También +1 para generadores. Otras soluciones requieren una lenllamada y, por lo tanto, no funcionan en otros generadores.
Cuadue
12
from itertools import izip_longest

def chunker(iterable, chunksize, filler):
    return izip_longest(*[iter(iterable)]*chunksize, fillvalue=filler)
Pedro Henriques
fuente
Una forma legible de hacerlo es stackoverflow.com/questions/434287/…
jfs
Tenga en cuenta que en python 3 izip_longestse reemplaza porzip_longest
mdmjsh
11

La solución ideal para este problema funciona con iteradores (no solo secuencias). También debería ser rápido.

Esta es la solución proporcionada por la documentación para itertools:

def grouper(n, iterable, fillvalue=None):
    #"grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return itertools.izip_longest(fillvalue=fillvalue, *args)

Usando ipython %timeiten mi Mac Book Air, obtengo 47.5 us por bucle.

Sin embargo, esto realmente no funciona para mí, ya que los resultados se rellenan para ser grupos de tamaño uniforme. Una solución sin el relleno es un poco más complicada. La solución más ingenua podría ser:

def grouper(size, iterable):
    i = iter(iterable)
    while True:
        out = []
        try:
            for _ in range(size):
                out.append(i.next())
        except StopIteration:
            yield out
            break

        yield out

Simple, pero bastante lento: 693 us por ciclo

La mejor solución que podría encontrar para usos islicedel bucle interno:

def grouper(size, iterable):
    it = iter(iterable)
    while True:
        group = tuple(itertools.islice(it, None, size))
        if not group:
            break
        yield group

Con el mismo conjunto de datos, obtengo 305 us por ciclo.

Al no poder obtener una solución pura más rápido que eso, proporciono la siguiente solución con una advertencia importante: si sus datos de entrada tienen instancias filldata, podría obtener una respuesta incorrecta.

def grouper(n, iterable, fillvalue=None):
    #"grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    for i in itertools.izip_longest(fillvalue=fillvalue, *args):
        if tuple(i)[-1] == fillvalue:
            yield tuple(v for v in i if v != fillvalue)
        else:
            yield i

Realmente no me gusta esta respuesta, pero es significativamente más rápida. 124 us por lazo

rhettg
fuente
Se puede reducir el tiempo de ejecución para la receta # 3 por ~ 10-15% moviéndolo a la capa C (omitiendo itertoolslas importaciones; mapdebe ser el AP3 mapo imap): def grouper(n, it): return takewhile(bool, map(tuple, starmap(islice, repeat((iter(it), n))))). Su función final puede hacerse menos frágil utilizando un centinela: deshacerse del fillvalueargumento; agregue una primera línea fillvalue = object(), luego cambie el ifcheque a if i[-1] is fillvalue:y la línea a la que controla yield tuple(v for v in i if v is not fillvalue). Garantías que ningún valor iterablepuede confundirse con el valor de relleno.
ShadowRanger
Por cierto, un gran aprobado en el n. ° 4. Estaba a punto de publicar mi optimización del n. ° 3 como una mejor respuesta (en cuanto al rendimiento) que lo que se había publicado hasta ahora, pero con el ajuste para que sea confiable, el n. ° 4 resistente supera el doble de rápido que el n. ° 3 optimizado; No esperaba una solución con bucles de nivel Python (y sin diferencias algorítmicas teóricas AFAICT) para ganar. Supongo que el n. ° 3 pierde debido al gasto de construcción / iteración de isliceobjetos (el n. ° 3 gana si nes relativamente grande, por ejemplo, el número de grupos es pequeño, pero eso es optimizar para un caso poco común), pero no esperaba que fuera bastante extremo.
ShadowRanger
Para el n. ° 4, la primera rama del condicional solo se toma en la última iteración (la tupla final). En vez de reconstituir la tupla final de todo de nuevo, almacenar en caché el módulo de la longitud de la iterables originales en la parte superior y el uso que para rebanar el relleno no deseado de izip_longestla tupla final: yield i[:modulo]. Además, para la argsvariable tupla en lugar de una lista: args = (iter(iterable),) * n. Afeita algunos ciclos de reloj más. Por último, si ignoramos el valor de relleno y asumimos None, el condicional puede convertirse if None in ien incluso más ciclos de reloj.
Kumba
1
@Kumba: Su primera sugerencia asume que la entrada tiene longitud conocida. Si es un iterador / generador, no una colección con una longitud conocida, no hay nada que almacenar en caché. No hay ninguna razón real para usar una optimización de este tipo; está optimizando el caso poco común (el último yield), mientras que el caso común no se ve afectado.
ShadowRanger
10

Necesitaba una solución que también funcionara con conjuntos y generadores. No se me ocurrió nada muy corto y bonito, pero al menos es bastante legible.

def chunker(seq, size):
    res = []
    for el in seq:
        res.append(el)
        if len(res) == size:
            yield res
            res = []
    if res:
        yield res

Lista:

>>> list(chunker([i for i in range(10)], 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

Conjunto:

>>> list(chunker(set([i for i in range(10)]), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

Generador:

>>> list(chunker((i for i in range(10)), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
bcoughlan
fuente
8

Similar a otras propuestas, pero no exactamente idéntico, me gusta hacerlo de esta manera, porque es simple y fácil de leer:

it = iter([1, 2, 3, 4, 5, 6, 7, 8, 9])
for chunk in zip(it, it, it, it):
    print chunk

>>> (1, 2, 3, 4)
>>> (5, 6, 7, 8)

De esta manera no obtendrá el último fragmento parcial. Si desea obtener el (9, None, None, None)último fragmento, solo use izip_longestde itertools.

kriss
fuente
se puede mejorar conzip(*([it]*4))
Jean-François Fabre
@ Jean-François Fabre: desde el punto de vista de la legibilidad, no lo veo como una mejora. Y también es marginalmente más lento. Es una mejora si estás jugando al golf, y yo no.
kriss el
no, no estoy jugando al golf, pero ¿qué pasa si tienes 10 argumentos? Leí esa construcción en alguna página oficial. Pero, por supuesto, parece que no puedo encontrarla ahora :)
Jean-François Fabre
@ Jean-François Fabre: si tengo 10 argumentos, o un número variable de argumentos, es una opción, pero prefiero escribir: zip (* (it,) * 10)
kriss
¡Correcto! Eso es lo que leo. no es la lista de cosas que he hecho :)
Jean-François Fabre
8

Si no le importa usar un paquete externo, puede usar iteration_utilities.grouperdesde 1 . Admite todos los iterables (no solo las secuencias):iteration_utilties

from iteration_utilities import grouper
seq = list(range(20))
for group in grouper(seq, 4):
    print(group)

que imprime:

(0, 1, 2, 3)
(4, 5, 6, 7)
(8, 9, 10, 11)
(12, 13, 14, 15)
(16, 17, 18, 19)

En caso de que la longitud no sea un múltiplo del tamaño de grupo, también admite llenar (el último grupo incompleto) o truncar (descartar el último grupo incompleto) el último:

from iteration_utilities import grouper
seq = list(range(17))
for group in grouper(seq, 4):
    print(group)
# (0, 1, 2, 3)
# (4, 5, 6, 7)
# (8, 9, 10, 11)
# (12, 13, 14, 15)
# (16,)

for group in grouper(seq, 4, fillvalue=None):
    print(group)
# (0, 1, 2, 3)
# (4, 5, 6, 7)
# (8, 9, 10, 11)
# (12, 13, 14, 15)
# (16, None, None, None)

for group in grouper(seq, 4, truncate=True):
    print(group)
# (0, 1, 2, 3)
# (4, 5, 6, 7)
# (8, 9, 10, 11)
# (12, 13, 14, 15)

Puntos de referencia

También decidí comparar el tiempo de ejecución de algunos de los enfoques mencionados. Es una trama log-log que se agrupa en grupos de elementos "10" en función de una lista de diferentes tamaños. Para resultados cualitativos: Menor significa más rápido:

ingrese la descripción de la imagen aquí

Al menos en este punto de referencia, el iteration_utilities.groupermejor rendimiento. Seguido por el enfoque de Craz .

El punto de referencia fue creado con 1 . El código utilizado para ejecutar este punto de referencia fue:simple_benchmark

import iteration_utilities
import itertools
from itertools import zip_longest

def consume_all(it):
    return iteration_utilities.consume(it, None)

import simple_benchmark
b = simple_benchmark.BenchmarkBuilder()

@b.add_function()
def grouper(l, n):
    return consume_all(iteration_utilities.grouper(l, n))

def Craz_inner(iterable, n, fillvalue=None):
    args = [iter(iterable)] * n
    return zip_longest(*args, fillvalue=fillvalue)

@b.add_function()
def Craz(iterable, n, fillvalue=None):
    return consume_all(Craz_inner(iterable, n, fillvalue))

def nosklo_inner(seq, size):
    return (seq[pos:pos + size] for pos in range(0, len(seq), size))

@b.add_function()
def nosklo(seq, size):
    return consume_all(nosklo_inner(seq, size))

def SLott_inner(ints, chunk_size):
    for i in range(0, len(ints), chunk_size):
        yield ints[i:i+chunk_size]

@b.add_function()
def SLott(ints, chunk_size):
    return consume_all(SLott_inner(ints, chunk_size))

def MarkusJarderot1_inner(iterable,size):
    it = iter(iterable)
    chunk = tuple(itertools.islice(it,size))
    while chunk:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

@b.add_function()
def MarkusJarderot1(iterable,size):
    return consume_all(MarkusJarderot1_inner(iterable,size))

def MarkusJarderot2_inner(iterable,size,filler=None):
    it = itertools.chain(iterable,itertools.repeat(filler,size-1))
    chunk = tuple(itertools.islice(it,size))
    while len(chunk) == size:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

@b.add_function()
def MarkusJarderot2(iterable,size):
    return consume_all(MarkusJarderot2_inner(iterable,size))

@b.add_arguments()
def argument_provider():
    for exp in range(2, 20):
        size = 2**exp
        yield size, simple_benchmark.MultiArgument([[0] * size, 10])

r = b.run()

1 Descargo de responsabilidad: soy el autor de las bibliotecas iteration_utilitiesy simple_benchmark.

MSeifert
fuente
7

Como nadie lo ha mencionado todavía, aquí hay una zip()solución:

>>> def chunker(iterable, chunksize):
...     return zip(*[iter(iterable)]*chunksize)

Funciona solo si la longitud de su secuencia siempre es divisible por el tamaño del fragmento o si no le importa un fragmento final si no lo es.

Ejemplo:

>>> s = '1234567890'
>>> chunker(s, 3)
[('1', '2', '3'), ('4', '5', '6'), ('7', '8', '9')]
>>> chunker(s, 4)
[('1', '2', '3', '4'), ('5', '6', '7', '8')]
>>> chunker(s, 5)
[('1', '2', '3', '4', '5'), ('6', '7', '8', '9', '0')]

O usando itertools.izip para devolver un iterador en lugar de una lista:

>>> from itertools import izip
>>> def chunker(iterable, chunksize):
...     return izip(*[iter(iterable)]*chunksize)

El relleno se puede arreglar usando la respuesta de @ ΤΖΩΤΖΙΟΥ :

>>> from itertools import chain, izip, repeat
>>> def chunker(iterable, chunksize, fillvalue=None):
...     it   = chain(iterable, repeat(fillvalue, chunksize-1))
...     args = [it] * chunksize
...     return izip(*args)
jfs
fuente
5

El uso de map () en lugar de zip () soluciona el problema de relleno en la respuesta de JF Sebastian:

>>> def chunker(iterable, chunksize):
...   return map(None,*[iter(iterable)]*chunksize)

Ejemplo:

>>> s = '1234567890'
>>> chunker(s, 3)
[('1', '2', '3'), ('4', '5', '6'), ('7', '8', '9'), ('0', None, None)]
>>> chunker(s, 4)
[('1', '2', '3', '4'), ('5', '6', '7', '8'), ('9', '0', None, None)]
>>> chunker(s, 5)
[('1', '2', '3', '4', '5'), ('6', '7', '8', '9', '0')]
pasarela
fuente
2
Esto se maneja mejor con itertools.izip_longest(Py2) / itertools.zip_longest(Py3); este uso de mapes doblemente obsoleto y no está disponible en Py3 (no puede pasar Nonecomo la función de mapeador, y se detiene cuando se agota el iterable más corto, no el más largo; no se rellena).
ShadowRanger
4

Otro enfoque sería utilizar la forma de dos argumentos de iter:

from itertools import islice

def group(it, size):
    it = iter(it)
    return iter(lambda: tuple(islice(it, size)), ())

Esto se puede adaptar fácilmente para usar relleno (esto es similar a la respuesta de Markus Jarderot ):

from itertools import islice, chain, repeat

def group_pad(it, size, pad=None):
    it = chain(iter(it), repeat(pad))
    return iter(lambda: tuple(islice(it, size)), (pad,) * size)

Incluso se pueden combinar para un relleno opcional:

_no_pad = object()
def group(it, size, pad=_no_pad):
    if pad == _no_pad:
        it = iter(it)
        sentinel = ()
    else:
        it = chain(iter(it), repeat(pad))
        sentinel = (pad,) * size
    return iter(lambda: tuple(islice(it, size)), sentinel)
senderle
fuente
1
¡preferible porque tiene la opción de omitir el relleno!
n611x007
3

Si la lista es grande, la forma de mayor rendimiento para hacerlo será usar un generador:

def get_chunk(iterable, chunk_size):
    result = []
    for item in iterable:
        result.append(item)
        if len(result) == chunk_size:
            yield tuple(result)
            result = []
    if len(result) > 0:
        yield tuple(result)

for x in get_chunk([1,2,3,4,5,6,7,8,9,10], 3):
    print x

(1, 2, 3)
(4, 5, 6)
(7, 8, 9)
(10,)
Robert Rossney
fuente
(Creo que la sugerencia de itertools de MizardX es funcionalmente equivalente a esto.)
Robert Rossney el
1
(En realidad, al reflexionar, no, no lo hago. Itertools.islice devuelve un iterador, pero no usa uno existente.)
Robert Rossney el
Es agradable y simple, pero por alguna razón, incluso sin conversión a tupla 4-7 veces más lenta que el método de mero aceptado en iterable = range(100000000)y chunksizehasta 10000.
Valentas
Sin embargo, en general, recomendaría este método, porque el aceptado puede ser extremadamente lento cuando se verifica que el último elemento sea lento docs.python.org/3/library/itertools.html#itertools.zip_longest
Valentas
3

Usar pequeñas funciones y cosas realmente no me atrae; Prefiero usar solo rebanadas:

data = [...]
chunk_size = 10000 # or whatever
chunks = [data[i:i+chunk_size] for i in xrange(0,len(data),chunk_size)]
for chunk in chunks:
    ...
Será
fuente
agradable pero no es bueno para una secuencia indefinida que no se conoce len. puedes hacer una prueba con itertools.repeato itertools.cycle.
n611x007
1
Además, consume memoria debido al uso de una [...for...] comprensión de la lista para construir físicamente una lista en lugar de usar una (...for...) expresión generadora que solo se preocuparía por el siguiente elemento y la memoria
adicional
2

Para evitar todas las conversiones a una lista import itertoolsy:

>>> for k, g in itertools.groupby(xrange(35), lambda x: x/10):
...     list(g)

Produce:

... 
0 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1 [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
2 [20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
3 [30, 31, 32, 33, 34]
>>> 

Lo verifiqué groupbyy no se convierte en lista o usolen así que (creo) esto retrasará la resolución de cada valor hasta que realmente se use. Lamentablemente, ninguna de las respuestas disponibles (en este momento) parecía ofrecer esta variación.

Obviamente, si necesita manejar cada elemento a su vez, anide un bucle for sobre g:

for k,g in itertools.groupby(xrange(35), lambda x: x/10):
    for i in g:
       # do what you need to do with individual items
    # now do what you need to do with the whole group

Mi interés específico en esto fue la necesidad de consumir un generador para enviar cambios en lotes de hasta 1000 a la API de gmail:

    messages = a_generator_which_would_not_be_smart_as_a_list
    for idx, batch in groupby(messages, lambda x: x/1000):
        batch_request = BatchHttpRequest()
        for message in batch:
            batch_request.add(self.service.users().messages().modify(userId='me', id=message['id'], body=msg_labels))
        http = httplib2.Http()
        self.credentials.authorize(http)
        batch_request.execute(http=http)
John Mee
fuente
¿Qué sucede si la lista que está fragmentando no es una secuencia de enteros ascendentes?
PaulMcG
@PaulMcGuire ver groupby ; dada una función para describir el orden, entonces los elementos del iterable pueden ser cualquier cosa, ¿verdad?
John Mee el
1
Sí, estoy familiarizado con groupby. Pero si los mensajes fueran las letras "ABCDEFG", entonces groupby(messages, lambda x: x/3)le daría un TypeError (por tratar de dividir una cadena por un int), no agrupaciones de 3 letras. Ahora, si lo hicieras groupby(enumerate(messages), lambda x: x[0]/3), podrías tener algo. Pero no dijiste eso en tu publicación.
PaulMcG
2

Con NumPy es simple:

ints = array([1, 2, 3, 4, 5, 6, 7, 8])
for int1, int2 in ints.reshape(-1, 2):
    print(int1, int2)

salida:

1 2
3 4
5 6
7 8
endolito
fuente
2
def chunker(iterable, n):
    """Yield iterable in chunk sizes.

    >>> chunks = chunker('ABCDEF', n=4)
    >>> chunks.next()
    ['A', 'B', 'C', 'D']
    >>> chunks.next()
    ['E', 'F']
    """
    it = iter(iterable)
    while True:
        chunk = []
        for i in range(n):
            try:
                chunk.append(next(it))
            except StopIteration:
                yield chunk
                raise StopIteration
        yield chunk

if __name__ == '__main__':
    import doctest

    doctest.testmod()
Kamil Sindi
fuente
2

A menos que pierda algo, no se ha mencionado la siguiente solución simple con expresiones generadoras. Se supone que se conocen tanto el tamaño como la cantidad de fragmentos (que suele ser el caso), y que no se requiere relleno:

def chunks(it, n, m):
    """Make an iterator over m first chunks of size n.
    """
    it = iter(it)
    # Chunks are presented as tuples.
    return (tuple(next(it) for _ in range(n)) for _ in range(m))
Alexey
fuente
1

En su segundo método, avanzaría al siguiente grupo de 4 haciendo esto:

ints = ints[4:]

Sin embargo, no he hecho ninguna medición de rendimiento, así que no sé cuál podría ser más eficiente.

Dicho esto, generalmente elegiría el primer método. No es bonito, pero eso es a menudo una consecuencia de la interacción con el mundo exterior.

Greg Hewgill
fuente
1

Otra respuesta más, cuyas ventajas son:

1) Fácilmente comprensible
2) Funciona en cualquier secuencia iterable, no solo (algunas de las respuestas anteriores se ahogarán en los controladores de archivo)
3) No carga el fragmento en la memoria de una vez
4) No hace una lista de referencias largas el mismo iterador en la memoria
5) Sin relleno de valores de relleno al final de la lista

Dicho esto, no lo he cronometrado, por lo que podría ser más lento que algunos de los métodos más inteligentes, y algunas de las ventajas pueden ser irrelevantes dado el caso de uso.

def chunkiter(iterable, size):
  def inneriter(first, iterator, size):
    yield first
    for _ in xrange(size - 1): 
      yield iterator.next()
  it = iter(iterable)
  while True:
    yield inneriter(it.next(), it, size)

In [2]: i = chunkiter('abcdefgh', 3)
In [3]: for ii in i:                                                
          for c in ii:
            print c,
          print ''
        ...:     
        a b c 
        d e f 
        g h 

Actualización:
un par de inconvenientes debido al hecho de que los bucles interno y externo están extrayendo valores del mismo iterador:
1) continuar no funciona como se esperaba en el bucle externo: simplemente continúa al siguiente elemento en lugar de omitir un fragmento . Sin embargo, esto no parece ser un problema, ya que no hay nada que probar en el bucle externo.
2) el descanso no funciona como se esperaba en el bucle interno: el control terminará en el bucle interno nuevamente con el siguiente elemento en el iterador. Para omitir fragmentos enteros, envuelva el iterador interno (ii arriba) en una tupla, por ejemplo for c in tuple(ii), o establezca una bandera y agote el iterador.

elhefe
fuente
1
def group_by(iterable, size):
    """Group an iterable into lists that don't exceed the size given.

    >>> group_by([1,2,3,4,5], 2)
    [[1, 2], [3, 4], [5]]

    """
    sublist = []

    for index, item in enumerate(iterable):
        if index > 0 and index % size == 0:
            yield sublist
            sublist = []

        sublist.append(item)

    if sublist:
        yield sublist
Wilfred Hughes
fuente
+1 omite el relleno; la suya y bcoughlan 's es muy similar
n611x007
1

Puede usar la función de partición o fragmentos de la biblioteca funcy :

from funcy import partition

for a, b, c, d in partition(4, ints):
    foo += a * b * c * d

Estas funciones también tiene iterador versiones ipartitiony ichunks, lo que será más eficiente en este caso.

También puede echar un vistazo a su implementación .

Suor
fuente
1

Sobre la solución dada por J.F. Sebastian aquí :

def chunker(iterable, chunksize):
    return zip(*[iter(iterable)]*chunksize)

Es inteligente, pero tiene una desventaja: siempre devuelve la tupla. ¿Cómo obtener una cuerda en su lugar?
Por supuesto que puedes escribir''.join(chunker(...)) , pero la tupla temporal se construye de todos modos.

Puedes deshacerte de la tupla temporal escribiendo la tuya propia zip, así:

class IteratorExhausted(Exception):
    pass

def translate_StopIteration(iterable, to=IteratorExhausted):
    for i in iterable:
        yield i
    raise to # StopIteration would get ignored because this is generator,
             # but custom exception can leave the generator.

def custom_zip(*iterables, reductor=tuple):
    iterators = tuple(map(translate_StopIteration, iterables))
    while True:
        try:
            yield reductor(next(i) for i in iterators)
        except IteratorExhausted: # when any of iterators get exhausted.
            break

Entonces

def chunker(data, size, reductor=tuple):
    return custom_zip(*[iter(data)]*size, reductor=reductor)

Ejemplo de uso:

>>> for i in chunker('12345', 2):
...     print(repr(i))
...
('1', '2')
('3', '4')
>>> for i in chunker('12345', 2, ''.join):
...     print(repr(i))
...
'12'
'34'
GingerPlusPlus
fuente
2
No es una crítica para que usted cambie su respuesta, sino un comentario: el código es una responsabilidad. Cuanto más código escriba, más espacio creará para que los errores se oculten. Desde este punto de vista, reescribir en ziplugar de usar el existente no parece ser la mejor idea.
Alfe
1

Me gusta este enfoque. Se siente simple y no mágico y admite todos los tipos iterables y no requiere importaciones.

def chunk_iter(iterable, chunk_size):
it = iter(iterable)
while True:
    chunk = tuple(next(it) for _ in range(chunk_size))
    if not chunk:
        break
    yield chunk
BallpointBen
fuente
1

Nunca quiero mis pedazos acolchados, por lo que ese requisito es esencial. Creo que la capacidad de trabajar en cualquier iterable también es un requisito. Dado eso, decidí extender la respuesta aceptada, https://stackoverflow.com/a/434411/1074659 .

El rendimiento se ve afectado ligeramente en este enfoque si no se desea el relleno debido a la necesidad de comparar y filtrar los valores de relleno. Sin embargo, para trozos grandes, esta utilidad es muy eficaz.

#!/usr/bin/env python3
from itertools import zip_longest


_UNDEFINED = object()


def chunker(iterable, chunksize, fillvalue=_UNDEFINED):
    """
    Collect data into chunks and optionally pad it.

    Performance worsens as `chunksize` approaches 1.

    Inspired by:
        https://docs.python.org/3/library/itertools.html#itertools-recipes

    """
    args = [iter(iterable)] * chunksize
    chunks = zip_longest(*args, fillvalue=fillvalue)
    yield from (
        filter(lambda val: val is not _UNDEFINED, chunk)
        if chunk[-1] is _UNDEFINED
        else chunk
        for chunk in chunks
    ) if fillvalue is _UNDEFINED else chunks
franco
fuente
1

Aquí hay un trozo sin importaciones que admite generadores:

def chunks(seq, size):
    it = iter(seq)
    while True:
        ret = tuple(next(it) for _ in range(size))
        if len(ret) == size:
            yield ret
        else:
            raise StopIteration()

Ejemplo de uso:

>>> def foo():
...     i = 0
...     while True:
...         i += 1
...         yield i
...
>>> c = chunks(foo(), 3)
>>> c.next()
(1, 2, 3)
>>> c.next()
(4, 5, 6)
>>> list(chunks('abcdefg', 2))
[('a', 'b'), ('c', 'd'), ('e', 'f')]
Cuadue
fuente
1

Con Python 3.8 puede usar el operador de morsa y itertools.islice.

from itertools import islice

list_ = [i for i in range(10, 100)]

def chunker(it, size):
    iterator = iter(it)
    while chunk := list(islice(iterator, size)):
        print(chunk)
In [2]: chunker(list_, 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, 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]
kafran
fuente
0

No parece haber una buena manera de hacer esto. Aquí hay una página que tiene varios métodos, que incluyen:

def split_seq(seq, size):
    newseq = []
    splitsize = 1.0/size*len(seq)
    for i in range(size):
        newseq.append(seq[int(round(i*splitsize)):int(round((i+1)*splitsize))])
    return newseq
Harley Holcombe
fuente
0

Si las listas son del mismo tamaño, puede combinarlas en listas de 4 tuplas con zip(). Por ejemplo:

# Four lists of four elements each.

l1 = range(0, 4)
l2 = range(4, 8)
l3 = range(8, 12)
l4 = range(12, 16)

for i1, i2, i3, i4 in zip(l1, l2, l3, l4):
    ...

Esto es lo zip()que produce la función:

>>> print l1
[0, 1, 2, 3]
>>> print l2
[4, 5, 6, 7]
>>> print l3
[8, 9, 10, 11]
>>> print l4
[12, 13, 14, 15]
>>> print zip(l1, l2, l3, l4)
[(0, 4, 8, 12), (1, 5, 9, 13), (2, 6, 10, 14), (3, 7, 11, 15)]

Si las listas son grandes y no desea combinarlas en una lista más grande, use itertools.izip(), que produce un iterador, en lugar de una lista.

from itertools import izip

for i1, i2, i3, i4 in izip(l1, l2, l3, l4):
    ...
Brian Clapper
fuente
0

Solución ad hoc de una sola línea para iterar sobre una lista xen trozos de tamaño 4:

for a, b, c, d in zip(x[0::4], x[1::4], x[2::4], x[3::4]):
    ... do something with a, b, c and d ...
Tutul
fuente