La forma más limpia de obtener el último elemento del iterador de Python

110

¿Cuál es la mejor manera de obtener el último elemento de un iterador en Python 2.6? Por ejemplo, di

my_iter = iter(range(5))

¿Cuál es la más corta de código / forma más limpia de conseguir 4a partir my_iter?

Podría hacer esto, pero no parece muy eficiente:

[x for x in my_iter][-1]
Pedro
fuente
4
Los iteradores asumen que desea iterar a través de los elementos y no acceder a los últimos elementos realmente. ¿Qué le impide simplemente usar range (5) [- 1]?
Frank
7
@Frank: asumí que el iterador real era más complejo y / o más alejado y / o más difícil de controlar queiter(range(5))
Chris Lutz
3
@Frank: el hecho de que en realidad es una función generadora mucho más complicada la que suministra el iterador. Simplemente inventé este ejemplo para que fuera simple y claro lo que estaba sucediendo.
Peter
4
Si desea el último elemento de un iterador, existe una gran posibilidad de que esté haciendo algo mal. Pero la respuesta es que en realidad no existe una forma más limpia de iterar a través del iterador. Esto se debe a que los iteradores no tienen un tamaño y, de hecho, es posible que nunca terminen y, como tal, es posible que no tengan un último elemento. (Lo que significa que su código se ejecutará para siempre, por supuesto). Entonces, la pregunta persistente es: ¿Por qué desea el último elemento de un iterador?
Lennart Regebro
3
@Peter: Actualiza tu pregunta, por favor. No agregue muchos comentarios a una pregunta que sea de su propiedad. Actualice la pregunta y elimine los comentarios.
S.Lott

Respuestas:

100
item = defaultvalue
for item in my_iter:
    pass
Thomas Wouters
fuente
4
¿Por qué el marcador de posición "valor predeterminado"? ¿Por qué no None? Para eso Nonees precisamente . ¿Sugiere que algún valor predeterminado específico de la función podría incluso ser correcto? Si el iterador en realidad no itera, entonces un valor fuera de banda es más significativo que algún valor predeterminado engañoso específico de la función.
S.Lott
46
El valor predeterminado es solo un marcador de posición para mi ejemplo. Si desea utilizarlo Nonecomo valor predeterminado, esa es su elección. Ninguno no siempre es el valor predeterminado más sensato y es posible que ni siquiera esté fuera de banda. Personalmente, tiendo a usar 'defaultvalue = object ()' para asegurarme de que sea un valor verdaderamente único. Solo estoy indicando que la opción predeterminada está fuera del alcance de este ejemplo.
Thomas Wouters
28
@ S.Lott: quizás sea útil distinguir la diferencia entre un iterador vacío y un iterador que tiene Nonecomo valor final
John La Rooy
8
¿Hay un error de diseño en todos los iteradores de todos los tipos de contenedores integrados? Primera vez que oí hablar de él :)
Thomas Wouters
7
Si bien esta es probablemente la solución más rápida, depende de que la variable se filtre en bucles for (una característica para algunos, un error para otros; probablemente los chicos de FP están horrorizados). De todos modos, Guido dijo que esto siempre funcionará de esta manera, por lo que es una construcción segura de usar.
tokland
68

Utilice una dequetalla 1.

from collections import deque

#aa is an interator
aa = iter('apple')

dd = deque(aa, maxlen=1)
last_element = dd.pop()
martin23487234
fuente
6
Esta es en realidad la forma más rápida de agotar una secuencia larga, aunque solo un poco más rápido que el bucle for.
Sven Marnach
11
+1 por ser técnicamente correcto, pero los lectores deben tener las advertencias habituales de Python: "¿REALMENTE necesitas optimizar esto?", "Esto es menos explícito, que no es Pythonic" y "La velocidad más rápida depende de la implementación, que podría cambiar."
leewz
1
también, es un acaparador de memoria
Eelco Hoogendoorn
6
@EelcoHoogendoorn ¿Por qué acapara la memoria, incluso con un máximo de 1?
Chris Wesseling
1
De todas las soluciones presentadas aquí hasta ahora, considero que esta es la más rápida y la más eficiente en memoria .
Markus Strauss
66

Si está utilizando Python 3.x:

*_, last = iterator # for a better understanding check PEP 448
print(last)

si está utilizando Python 2.7:

last = next(iterator)
for last in iterator:
    continue
print last


Nota al margen:

Por lo general, la solución presentada anteriormente es la que necesita para casos regulares, pero si está tratando con una gran cantidad de datos, es más eficiente usar un dequetamaño 1. ( fuente )

from collections import deque

#aa is an interator
aa = iter('apple')

dd = deque(aa, maxlen=1)
last_element = dd.pop()
DhiaTN
fuente
1
@virtualxtc: el guión bajo es solo un identificador. La estrella al frente dice "expandir la lista". El más legible sería *lst, last = some_iterable.
pepr
4
@virtualxtc nope _es una variable especial en Python y se usa para almacenar el último valor o para decir que no me importa el valor, por lo que podría limpiarse.
DhiaTN
1
Esa solución de Python 3 no es eficiente en memoria.
Markus Strauss
3
@DhiaTN Sí, tienes toda la razón. De hecho, me gusta mucho el modismo de Python 3 que mostraste. Solo quería dejar en claro que no funciona para "big data". Para eso utilizo collections.deque, que resulta ser rápido y eficiente en la memoria (ver la solución de martin23487234).
Markus Strauss
1
Este ejemplo de py3.5 + debería estar en PEP 448. Maravilloso.
EliadL
33

Probablemente valga la pena usarlo __reversed__si está disponible

if hasattr(my_iter,'__reversed__'):
    last = next(reversed(my_iter))
else:
    for last in my_iter:
        pass
John La Rooy
fuente
27

Tan simple como:

max(enumerate(the_iter))[1]
Chema Cortes
fuente
8
Oh, esto es inteligente. No es el más eficiente o legible, pero inteligente.
timgeb
6
Entonces, solo pensar en voz alta ... Esto funciona porque enumeratedevuelve (index, value)como: (0, val0), (1, val1), (2, val2)... y luego, de manera predeterminada, maxcuando se le da una lista de tuplas, se compara solo con el primer valor de la tupla, a menos que los dos primeros valores sean iguales, que nunca están aquí porque representan índices. Entonces, el subíndice final se debe a que max devuelve la tupla completa (idx, value) mientras que solo nos interesa value. Idea interesante.
Taylor Edmiston
21

Es poco probable que esto sea más rápido que el bucle for vacío debido a la lambda, pero tal vez le dé a otra persona una idea

reduce(lambda x,y:y,my_iter)

Si el iter está vacío, se genera un TypeError

John La Rooy
fuente
En mi humilde opinión, este es el más directo, conceptualmente. En vez de levantar TypeErrorpara un iterable vacío, también se puede suministrar un valor predeterminado mediante el valor inicial de reduce(), por ejemplo, last = lambda iterable, default=None: reduce(lambda _, x: x, iterable, default).
egnha
9

Hay esto

list( the_iter )[-1]

Si la duración de la iteración es realmente épica, tan larga que materializar la lista agotará la memoria, entonces realmente necesitas repensar el diseño.

S.Lott
fuente
1
Ésta es la solución más sencilla.
laike9m
2
Un poco mejor usar una tupla.
Christopher Smith
9
Totalmente en desacuerdo con la última oración. Trabajar con conjuntos de datos muy grandes (que podrían exceder los límites de la memoria si se cargan todos a la vez) es la razón principal para usar un iterador en lugar de una lista.
Paul
@Paul: algunas funciones solo devuelven un iterador. Esta es una forma corta y bastante legible de hacerlo en ese caso (para listas no épicas).
serv-inc
Esa es la forma menos eficiente que uno debe evitar como un mal mal mal hábito. Otro es usar sort (secuencia) [- 1] para obtener el elemento máximo de la secuencia. Por favor, nunca use estos patrones negativos si le gusta ser ingeniero de software.
Maksym Ganenko
5

Yo usaría reversed, excepto que solo toma secuencias en lugar de iteradores, lo que parece bastante arbitrario.

De cualquier forma que lo haga, tendrá que ejecutar todo el iterador. Con la máxima eficiencia, si no necesita el iterador nunca más, puede simplemente eliminar todos los valores:

for last in my_iter:
    pass
# last is now the last item

Sin embargo, creo que esta es una solución subóptima.

Chris Lutz
fuente
4
reversed () no toma un iterador, solo secuencias.
Thomas Wouters
3
No es nada arbitrario. La única forma de revertir un iterador es iterar hasta el final, mientras se mantienen todos los elementos en la memoria. Yo, e, primero necesitas hacer una secuencia a partir de él, antes de poder revertirlo. Lo que, por supuesto, frustra el propósito del iterador en primer lugar, y también significaría que de repente gastas mucha memoria sin ninguna razón aparente. De hecho, es lo contrario de arbitrario. :)
Lennart Regebro
@Lennart - Cuando dije arbitrario, quise decir molesto. Estoy enfocando mis habilidades lingüísticas en mi trabajo que se entregará en unas horas a esta hora de la mañana.
Chris Lutz
3
Lo suficientemente justo. Aunque en mi opinión, sería más molesto si aceptara iteradores, porque casi cualquier uso sería una mala idea (tm). :)
Lennart Regebro
3

La biblioteca toolz proporciona una buena solución:

from toolz.itertoolz import last
last(values)

Pero agregar una dependencia no central puede que no valga la pena por usarla solo en este caso.

lumbric
fuente
0

Yo solo usaría next(reversed(myiter))

thomas.mac
fuente
8
TypeError: el argumento de reversed () debe ser una secuencia
Labo
0

La pregunta es sobre cómo obtener el último elemento de un iterador, pero si su iterador se crea aplicando condiciones a una secuencia, entonces se puede usar reversed para encontrar el "primero" de una secuencia invertida, solo mirando los elementos necesarios, aplicando inversa a la secuencia en sí.

Un ejemplo artificial

>>> seq = list(range(10))
>>> last_even = next(_ for _ in reversed(seq) if _ % 2 == 0)
>>> last_even
8
Wyrmwood
fuente
0

Alternativamente, para iteradores infinitos puede usar:

from itertools import islice 
last = list(islice(iterator(), 1000))[-1] # where 1000 is number of samples 

Pensé que sería más lento entonces, dequepero es tan rápido y en realidad es más rápido que para el método de bucle (de alguna manera)

qocu
fuente
-6

La pregunta es incorrecta y solo puede dar lugar a una respuesta complicada e ineficaz. Para obtener un iterador, por supuesto, comienza con algo que sea iterable, que en la mayoría de los casos ofrecerá una forma más directa de acceder al último elemento.

Una vez que crea un iterador a partir de un iterable, está atrapado en el análisis de los elementos, porque eso es lo único que proporciona un iterable.

Entonces, la forma más eficiente y clara es no crear el iterador en primer lugar, sino utilizar los métodos de acceso nativos del iterable.

Ludwig
fuente
5
Entonces, ¿cómo obtendría la última línea de un archivo?
Brice M. Dempsey
@ BriceM.Dempsey La mejor manera no es iterar a través de todo el archivo (tal vez enorme), sino ir al tamaño del archivo menos 100, leer los últimos 100 bytes, buscar una nueva línea en ellos, si no hay ninguna, ir retroceder otros 100 bytes, etc. También puede aumentar el tamaño de su retroceso, según su escenario. Leer un millón de líneas definitivamente no es una solución óptima.
Alfe