with tutorial framework espaƱol djangoproject desde con cero applications python list zip slice idioms

python - framework - tutorial django



Pares de una sola lista (5)

A menudo, he encontrado la necesidad de procesar una lista por parejas. Me preguntaba cuál sería la manera pitónica y eficiente de hacerlo, y encontré esto en Google:

pairs = zip(t[::2], t[1::2])

Pensé que era lo suficientemente pitónico, pero después de una discusión reciente que involucraba expresiones idiomáticas versus eficiencia , decidí hacer algunas pruebas:

import time from itertools import islice, izip def pairs_1(t): return zip(t[::2], t[1::2]) def pairs_2(t): return izip(t[::2], t[1::2]) def pairs_3(t): return izip(islice(t,None,None,2), islice(t,1,None,2)) A = range(10000) B = xrange(len(A)) def pairs_4(t): # ignore value of t! t = B return izip(islice(t,None,None,2), islice(t,1,None,2)) for f in pairs_1, pairs_2, pairs_3, pairs_4: # time the pairing s = time.time() for i in range(1000): p = f(A) t1 = time.time() - s # time using the pairs s = time.time() for i in range(1000): p = f(A) for a, b in p: pass t2 = time.time() - s print t1, t2, t2-t1

Estos fueron los resultados en mi computadora:

1.48668909073 2.63187503815 1.14518594742 0.105381965637 1.35109519958 1.24571323395 0.00257992744446 1.46182489395 1.45924496651 0.00251388549805 1.70076990128 1.69825601578

Si los estoy interpretando correctamente, eso debería significar que la implementación de listas, la indexación de listas y la división de listas en Python es muy eficiente. Es un resultado tanto reconfortante como inesperado.

¿Hay alguna otra forma "mejor" de atravesar una lista por parejas?

Tenga en cuenta que si la lista tiene un número impar de elementos, entonces el último no estará en ninguno de los pares.

¿Cuál sería la forma correcta de garantizar que todos los elementos estén incluidos?

Agregué estas dos sugerencias de las respuestas a las pruebas:

def pairwise(t): it = iter(t) return izip(it, it) def chunkwise(t, size=2): it = iter(t) return izip(*[it]*size)

Estos son los resultados:

0.00159502029419 1.25745987892 1.25586485863 0.00222492218018 1.23795199394 1.23572707176

Resultados hasta el momento

Lo más pitónico y muy eficiente:

pairs = izip(t[::2], t[1::2])

Más eficiente y muy pitónico:

pairs = izip(*[iter(t)]*2)

Me tomó un momento asimilar que la primera respuesta usa dos iteradores mientras que la segunda usa una sola.

Para tratar secuencias con un número impar de elementos, la sugerencia ha sido aumentar la secuencia original agregando un elemento ( None ) que se empareja con el último elemento anterior, algo que se puede lograr con itertools.izip_longest() .

Finalmente

Tenga en cuenta que, en Python 3.x, zip() comporta como itertools.izip() , e itertools.izip() se ha ido.


¿Hay alguna otra forma "mejor" de atravesar una lista por parejas?

No puedo decirlo con seguridad, pero lo dudo: cualquier otro recorrido incluiría más código de Python que debe ser interpretado. Las funciones integradas como zip () están escritas en C, que es mucho más rápido.

¿Cuál sería la forma correcta de garantizar que todos los elementos estén incluidos?

Compruebe la longitud de la lista y si es impar ( len(list) & 1 == 1 ), copie la lista y anexe un elemento.


Diría que la solución inicial de los pairs = zip(t[::2], t[1::2]) es la mejor porque es más fácil de leer (y en Python 3, zip devuelve automáticamente un iterador en lugar de una lista).

Para asegurarse de que todos los elementos estén incluidos, simplemente puede extender la lista por None .

Entonces, si la lista tiene un número impar de elementos, el último par será (item, None) .

>>> t = [1,2,3,4,5] >>> t.append(None) >>> zip(t[::2], t[1::2]) [(1, 2), (3, 4), (5, None)] >>> t = [1,2,3,4,5,6] >>> t.append(None) >>> zip(t[::2], t[1::2]) [(1, 2), (3, 4), (5, 6)]


Empiezo con un pequeño descargo de responsabilidad: no use el siguiente código. No es para nada pitónico, lo escribí solo por diversión. Es similar a @ THC4k pairwise function pero usa cierres iter y lambda . No utiliza el módulo itertools y no admite fillvalue . Lo puse aquí porque alguien podría encontrarlo interesante:

pairwise = lambda t: iter((lambda f: lambda: (f(), f()))(iter(t).next), None)


En cuanto a lo más pitónico, diría que las recetas proporcionadas en los documentos fuente de Python (algunas de las cuales se parecen mucho a las respuestas que proporcionó @JochenRitzel) es probablemente la mejor opción;)

def grouper(iterable, n, fillvalue=None): "Collect data into fixed-length chunks or blocks" # grouper(''ABCDEFG'', 3, ''x'') --> ABC DEF Gxx args = [iter(iterable)] * n return izip_longest(fillvalue=fillvalue, *args)


Mi forma favorita de hacerlo:

from itertools import izip def pairwise(t): it = iter(t) return izip(it,it) # for "pairs" of any length def chunkwise(t, size=2): it = iter(t) return izip(*[it]*size)

Cuando quiera emparejar todos los elementos, es posible que necesite un valor de relleno:

from itertools import izip_longest def blockwise(t, size=2, fillvalue=None): it = iter(t) return izip_longest(*[it]*size, fillvalue=fillvalue)