2010-04-23 16 views
11

Estoy buscando una forma pitónica de iterar sobre los primeros n elementos de un iterable (upd: no una lista en un caso común, como para las listas, las cosas son triviales), y es muy importante hacerlo lo más rápido posible. Así es como lo hago ahora:iteración rápida sobre los primeros n elementos de un iterable (no una lista) en python

count = 0 
for item in iterable: 
do_something(item) 
count += 1 
if count >= n: break 

No me parece limpio. Otra forma de hacer esto es:

for item in itertools.islice(iterable, n): 
    do_something(item) 

Esto se ve bien, la cuestión es lo suficientemente rápido para usar con algún generador (s)? Por ejemplo:

pair_generator = lambda iterable: itertools.izip(*[iter(iterable)]*2) 
for item in itertools.islice(pair_generator(iterable), n): 
so_something(item) 

¿Funcionará lo suficientemente rápido en comparación con el primer método? ¿Hay alguna manera más fácil de hacerlo?

+3

La única manera de responder a "lo suficientemente rápido" es establecer criterios de referencia a sí mismo. –

+0

Vea también: http://stackoverflow.com/questions/2688079/how-to-iterate-over-the-first-n-elements-of-a-list – outis

+0

¿Por qué es "bastante importante hacer esto tan rápido como posible"? ¿Puedes justificar esto con resultados de pstats para un caso de uso realista? Sospecho que su solución con 'islice' en realidad demostrará la mejor solución razonable para el rendimiento, pero por supuesto no sabemos sin el tiempo. –

Respuesta

14

for item in itertools.islice(iterable, n): es la forma más obvia y fácil para hacerlo. Funciona para iterables arbitrarios y es O (n), como cualquier solución sensata.

Es concebible que otra solución pueda tener un mejor rendimiento; no lo sabríamos sin tiempo. No recomendaría molestar con el tiempo a menos que profile su código y encontrar esta llamada para ser un punto de acceso. A menos que se entierre dentro de un bucle interno, es muy dudoso que lo sea. La optimización prematura es la fuente de todos los males.


Si era ir a buscar soluciones alternativas, me gustaría ver como los for count, item in enumerate(iterable): if count > n: break ... y for i in xrange(n): item = next(iterator) .... No creo que esto ayude, pero parece que valen la pena intentarlo si realmente queremos comparar cosas. Si estuve atascado en una situación en la que hice un perfil y encontré que era un punto de acceso en un bucle interno (¿esta es realmente tu situación?), También intentaría facilitar la búsqueda de nombre para obtener el atributo islice global iterools vinculando la función a un nombre local ya.

Estas son cosas que solo haces después de que hayas probado que te ayudarán. La gente intenta hacerlas otras veces mucho. No ayuda a que sus programas sean apreciablemente más rápidos; solo empeora sus programas.

+1

Muchas gracias por la respuesta, ¡me ha ayudado mucho! – martinthenext

+0

¡Bueno, deshacer enumerate también me parece muy bueno! En cuanto a la creación de perfiles y la búsqueda de puntos de acceso, este no es realmente mi caso, solo espero que algunos bucles en mi código tengan recuentos de iteraciones enormes, es por eso que hice una pregunta. Ahora lo entiendo: fue un error intentar la optimización en esta etapa, tengo que terminar el código y probarlo, y solo entonces optimizar las cosas, si es necesario. De nuevo, gracias por tu ayuda. – martinthenext

1

¿De una lista? Intenta

for k in mylist[0:n]: 
    # do stuff with k 

también se puede utilizar una comprensión si necesita

my_new_list = [blah(k) for k in mylist[0:n]] 
+0

sí, lo tengo. título incorrecto, lo siento, mi mal. – martinthenext

2

Si se trata de una lista, puede utilizar rebanar:

list[:n] 
6

itertools tiende a ser la solución más rápida, cuando sea directamente aplicable.

Obviamente, la única manera de comprobarlo es punto de referencia - por ejemplo, guardar en aaa.py

import itertools 

def doit1(iterable, n, do_something=lambda x: None): 
    count = 0 
    for item in iterable: 
    do_something(item) 
    count += 1 
    if count >= n: break 

def doit2(iterable, n, do_something=lambda x: None): 
    for item in itertools.islice(iterable, n): 
     do_something(item) 

pair_generator = lambda iterable: itertools.izip(*[iter(iterable)]*2) 

def dd1(itrbl=range(44)): doit1(itrbl, 23) 
def dd2(itrbl=range(44)): doit2(itrbl, 23) 

y ver ...:

$ python -mtimeit -s'import aaa' 'aaa.dd1()' 
100000 loops, best of 3: 8.82 usec per loop 
$ python -mtimeit -s'import aaa' 'aaa.dd2()' 
100000 loops, best of 3: 6.33 usec per loop 

tan claramente, itertools es más rápido aquí - punto de referencia con sus propios datos para verificar.

BTW, encuentro timeit MUCHO más utilizable desde la línea de comandos, así que así es como siempre lo uso - luego ejecuta el "orden de magnitud" correcto de bucles para el tipo de velocidades que está tratando específicamente de medir , sean esos 10, 100, 1000, etc., aquí, para distinguir un microsegundo y medio de diferencia, cien mil bucles es más o menos correcto.

+1

raro, es solo en contra de mi intuición cplusplus'ish ver una solución simple correr más despacio que uno ordenado. Python es el lenguaje más genial, de hecho. esto es una gran adición al consejo de Mike Graham de no hacer una optimización prematura. Supongo que la regla general es escribir lo que está bien, sin pensar en el tiempo de ejecución. – martinthenext

+1

@martin, personalmente, creo que un _lot_ sobre el tiempo de ejecución (sobre todo en términos de gran O para la escalabilidad) - pero, en general, los modismos más pitónicos suelen ser los que han sido más optimizados, porque 'usualmente son los que nos gustan más los que cometen Python (Hettinger, el autor de itertools y otras partes rápidas de Python, ha estado bastante activo en ese campo en los últimos años, como lo fue Peters en años anteriores, pero es bastante general fenómeno en la comunidad Python-committer). –

2

Se puede utilizar para escribir enumerate esencialmente el mismo bucle que tiene, pero de una manera más sencilla, Pythonic:

 
for idx, val in enumerate(iterableobj): 
    if idx > n: 
     break 
    do_something(val) 
+0

Esta opción se ha discutido anteriormente, parece una buena opción, pero creo que 'islice' es mejor porque no requiere ninguna variable adicional en el cuerpo del bucle, lo que hace que se vea más claro para mí – martinthenext

Cuestiones relacionadas