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?
Respuestas:
Modificado de la sección de recetas de los documentos de itertools de Python :
Ejemplo
en pseudocódigo para mantener el ejemplo conciso.
Nota: en Python 2 use en
izip_longest
lugar dezip_longest
.fuente
izip_longest
se alimentará con 256k argumentos.None
llenar el último fragmento?Simple. Fácil. Rápido. Funciona con cualquier secuencia:
fuente
itertools
módulo.chunker
devuelve agenerator
. Reemplace el retorno a:return [...]
para obtener una lista.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.__getitem__
método.Soy un fan de
fuente
chunk
tendrá 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 .De otra manera:
fuente
size
, lo que a veces es deseable.len
llamada y, por lo tanto, no funcionan en otros generadores.fuente
izip_longest
se reemplaza porzip_longest
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:
Usando ipython
%timeit
en 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:
Simple, pero bastante lento: 693 us por ciclo
La mejor solución que podría encontrar para usos
islice
del bucle interno: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.Realmente no me gusta esta respuesta, pero es significativamente más rápida. 124 us por lazo
fuente
itertools
las importaciones;map
debe ser el AP3map
oimap
):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 delfillvalue
argumento; agregue una primera líneafillvalue = object()
, luego cambie elif
cheque aif i[-1] is fillvalue:
y la línea a la que controlayield tuple(v for v in i if v is not fillvalue)
. Garantías que ningún valoriterable
puede confundirse con el valor de relleno.islice
objetos (el n. ° 3 gana sin
es 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.izip_longest
la tupla final:yield i[:modulo]
. Además, para laargs
variable 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 asumimosNone
, el condicional puede convertirseif None in i
en incluso más ciclos de reloj.yield
), mientras que el caso común no se ve afectado.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.
Lista:
Conjunto:
Generador:
fuente
Similar a otras propuestas, pero no exactamente idéntico, me gusta hacerlo de esta manera, porque es simple y fácil de leer:
De esta manera no obtendrá el último fragmento parcial. Si desea obtener el
(9, None, None, None)
último fragmento, solo useizip_longest
deitertools
.fuente
zip(*([it]*4))
Si no le importa usar un paquete externo, puede usar
iteration_utilities.grouper
desde 1 . Admite todos los iterables (no solo las secuencias):iteration_utilties
que imprime:
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:
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:
Al menos en este punto de referencia, el
iteration_utilities.grouper
mejor 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
1 Descargo de responsabilidad: soy el autor de las bibliotecas
iteration_utilities
ysimple_benchmark
.fuente
Como nadie lo ha mencionado todavía, aquí hay una
zip()
solución: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:
O usando itertools.izip para devolver un iterador en lugar de una lista:
El relleno se puede arreglar usando la respuesta de @ ΤΖΩΤΖΙΟΥ :
fuente
El uso de map () en lugar de zip () soluciona el problema de relleno en la respuesta de JF Sebastian:
Ejemplo:
fuente
itertools.izip_longest
(Py2) /itertools.zip_longest
(Py3); este uso demap
es doblemente obsoleto y no está disponible en Py3 (no puede pasarNone
como la función de mapeador, y se detiene cuando se agota el iterable más corto, no el más largo; no se rellena).Otro enfoque sería utilizar la forma de dos argumentos de
iter
:Esto se puede adaptar fácilmente para usar relleno (esto es similar a la respuesta de Markus Jarderot ):
Incluso se pueden combinar para un relleno opcional:
fuente
Si la lista es grande, la forma de mayor rendimiento para hacerlo será usar un generador:
fuente
iterable = range(100000000)
ychunksize
hasta 10000.Usar pequeñas funciones y cosas realmente no me atrae; Prefiero usar solo rebanadas:
fuente
len
. puedes hacer una prueba conitertools.repeat
oitertools.cycle
.[...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 memoriaPara evitar todas las conversiones a una lista
import itertools
y:Produce:
Lo verifiqué
groupby
y 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:
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:
fuente
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 hicierasgroupby(enumerate(messages), lambda x: x[0]/3)
, podrías tener algo. Pero no dijiste eso en tu publicación.Con NumPy es simple:
salida:
fuente
fuente
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:
fuente
En su segundo método, avanzaría al siguiente grupo de 4 haciendo esto:
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.
fuente
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.
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.fuente
fuente
Puede usar la función de partición o fragmentos de la biblioteca funcy :
Estas funciones también tiene iterador versiones
ipartition
yichunks
, lo que será más eficiente en este caso.También puede echar un vistazo a su implementación .
fuente
Sobre la solución dada por
J.F. Sebastian
aquí :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í:Entonces
Ejemplo de uso:
fuente
zip
lugar de usar el existente no parece ser la mejor idea.Me gusta este enfoque. Se siente simple y no mágico y admite todos los tipos iterables y no requiere importaciones.
fuente
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.
fuente
Aquí hay un trozo sin importaciones que admite generadores:
Ejemplo de uso:
fuente
Con Python 3.8 puede usar el operador de morsa y
itertools.islice
.fuente
No parece haber una buena manera de hacer esto. Aquí hay una página que tiene varios métodos, que incluyen:
fuente
Si las listas son del mismo tamaño, puede combinarlas en listas de 4 tuplas con
zip()
. Por ejemplo:Esto es lo
zip()
que produce la función: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.fuente
Solución ad hoc de una sola línea para iterar sobre una lista
x
en trozos de tamaño4
:fuente