Il modo più pulito per ottenere l'ultimo elemento dall'iteratore Python


110

Qual è il modo migliore per ottenere l'ultimo elemento da un iteratore in Python 2.6? Ad esempio, dì

my_iter = iter(range(5))

Qual è il codice più breve / il modo più pulito per ottenere 4da my_iter?

Potrei farlo, ma non sembra molto efficiente:

[x for x in my_iter][-1]

4
Gli iteratori presumono che tu voglia iterare attraverso gli elementi e non accedere davvero agli ultimi elementi. Cosa ti impedisce di utilizzare semplicemente l'intervallo (5) [- 1]?
Frank

7
@Frank - Ho pensato che l'iteratore effettivo fosse più complesso e / o più lontano e / o più difficile da controllare diiter(range(5))
Chris Lutz

3
@Frank: il fatto che in realtà sia una funzione generatore molto più complicata che fornisce l'iteratore. Ho inventato questo esempio in modo che fosse semplice e chiaro cosa stava succedendo.
Peter

4
Se vuoi l'ultimo elemento di un iteratore, c'è una grande possibilità che tu stia facendo qualcosa di sbagliato. Ma la risposta è che non esiste davvero un modo più pulito per iterare attraverso l'iteratore. Questo perché gli iteratori non hanno una dimensione e, in effetti, potrebbero non finire affatto e come tali potrebbero non avere un ultimo elemento. (Significa che il tuo codice verrà eseguito per sempre, ovviamente). Quindi la domanda persistente è: perché vuoi l'ultimo elemento di un iteratore?
Lennart Regebro

3
@Peter: aggiorna la tua domanda, per favore. Non aggiungere molti commenti a una domanda che possiedi. Aggiorna la domanda e rimuovi i commenti.
S.Lott

Risposte:


100
item = defaultvalue
for item in my_iter:
    pass

4
Perché il segnaposto "defaultvalue"? Perché no None? Questo è esattamente ciò che Noneserve. Stai suggerendo che qualche valore predefinito specifico della funzione potrebbe essere corretto? Se l'iteratore non esegue effettivamente l'iterazione, un valore fuori banda è più significativo di un valore predefinito fuorviante specifico della funzione.
S.Lott

46
Il valore predefinito è solo un segnaposto per il mio esempio. Se vuoi usare Nonecome valore predefinito, questa è la tua scelta. Nessuno non è sempre il valore predefinito più sensato e potrebbe non essere nemmeno fuori banda. Personalmente tendo a usare 'defaultvalue = object ()' per assicurarmi che sia un valore davvero unico. Sto solo indicando che la scelta dell'impostazione predefinita è al di fuori dell'ambito di questo esempio.
Thomas Wouters

28
@ S.Lott: forse è utile distinguere la differenza tra un iteratore vuoto e un iteratore che ha Nonecome valore finale
John La Rooy

8
C'è un errore di progettazione in tutti gli iteratori di tutti i tipi di container incorporati? La prima volta che ne ho sentito parlare :)
Thomas Wouters

7
Sebbene questa sia probabilmente la soluzione più veloce, si basa sulla variabile che perde nei cicli for (una funzionalità per alcuni, un bug per altri - probabilmente i ragazzi di FP sono sconvolti). Comunque, Guido ha detto che funzionerà sempre in questo modo, quindi è una costruzione sicura da usare.
tokland

68

Usa una dequedi taglia 1.

from collections import deque

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

dd = deque(aa, maxlen=1)
last_element = dd.pop()

6
Questo è in realtà il modo più veloce per esaurire una lunga sequenza, anche se solo leggermente più veloce del ciclo for.
Sven Marnach

11
+1 per essere tecnicamente corretto, ma i lettori dovrebbero avere i soliti avvertimenti di Python: "Hai DAVVERO bisogno di ottimizzarlo?", "Questo è meno esplicito, il che non è Pythonic" e "La velocità più alta dipende dall'implementazione, che potrebbe cambiare."
leewz

1
inoltre, è un maiale di memoria
Eelco Hoogendoorn

6
@EelcoHoogendoorn Perché è un divoratore di memoria, anche con un massimo di 1?
Chris Wesseling

1
Da tutte le soluzioni presentate qui finora, trovo che sia la più veloce ed efficiente in termini di memoria .
Markus Strauss

66

Se stai usando Python 3.x:

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

se stai usando python 2.7:

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


Nota a margine:

Di solito, la soluzione presentata sopra è ciò di cui hai bisogno per i casi normali, ma se hai a che fare con una grande quantità di dati, è più efficiente usare una dequedi dimensione 1. ( fonte )

from collections import deque

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

dd = deque(aa, maxlen=1)
last_element = dd.pop()

1
@virtualxtc: il trattino basso è solo un identificatore. La stella davanti dice "espandi l'elenco". Il più leggibile sarebbe *lst, last = some_iterable.
pepr

4
@virtualxtc no _è una variabile speciale in python e viene utilizzata per memorizzare l'ultimo valore o per dire che non mi interessa il valore, quindi potrebbe essere pulito.
DhiaTN

1
Quella soluzione Python 3 non è efficiente in termini di memoria.
Markus Strauss

3
@DhiaTN Sì, hai perfettamente ragione. In realtà, mi piace l'idioma di Python 3 che hai mostrato molto. Volevo solo chiarire che non funziona per i "big data". Uso collections.deque per questo, che risulta essere veloce ed efficiente in termini di memoria (vedere la soluzione da martin23487234).
Markus Strauss

1
Questo esempio py3.5 + dovrebbe essere in PEP 448. Meraviglioso.
EliadL

33

Probabilmente vale la pena usarlo __reversed__se è disponibile

if hasattr(my_iter,'__reversed__'):
    last = next(reversed(my_iter))
else:
    for last in my_iter:
        pass

27

Semplice come:

max(enumerate(the_iter))[1]

8
Oh, questo è intelligente. Non il più efficiente o leggibile, ma intelligente.
timgeb

6
Quindi solo pensare ad alta voce ... Funziona perché enumeraterestituisce (index, value)come: (0, val0), (1, val1), (2, val2)... e quindi per impostazione predefinita maxquando viene fornito un elenco di tuple, confronta solo il primo valore della tupla, a meno che due primi valori non siano uguali, cosa che non sono mai qui perché rappresentano indici. Quindi l'indice finale è perché max restituisce l'intera tupla (idx, value) mentre a noi interessa solo value. Idea interessante.
Taylor Edmiston

21

È improbabile che sia più veloce del ciclo for vuoto a causa del lambda, ma forse darà un'idea a qualcun altro

reduce(lambda x,y:y,my_iter)

Se l'iter è vuoto, viene sollevata un'eccezione TypeError


IMHO, questo è il più diretto, concettualmente. Invece di sollevare TypeErrorper un iterabile vuoto, si potrebbe anche fornire un valore di default tramite il valore iniziale reduce(), per esempio, last = lambda iterable, default=None: reduce(lambda _, x: x, iterable, default).
egnha

9

C'è questo

list( the_iter )[-1]

Se la lunghezza dell'iterazione è davvero epica - così lunga che materializzare l'elenco esaurirà la memoria - allora devi davvero ripensare al design.


1
Questa è la soluzione più semplice.
laike9m

2
Leggermente meglio usare una tupla.
Christopher Smith,

9
Assolutamente in disaccordo con l'ultima frase. Lavorare con set di dati molto grandi (che potrebbero superare i limiti di memoria se caricati tutti in una volta) è il motivo principale per utilizzare un iteratore invece di un elenco.
Paul,

@Paul: alcune funzioni restituiscono solo un iteratore. Questo è un modo breve e abbastanza leggibile per farlo in quel caso (per elenchi non epici).
serv-inc

Questo è il modo meno efficiente che si dovrebbe evitare come cattiva cattiva cattiva abitudine. Un altro è usare sort (sequence) [- 1] per ottenere l'elemento max della sequenza. Per favore non usare mai questi paterni malati se ti piace essere un ingegnere del software.
Maksym Ganenko

5

Lo userei reversed, tranne per il fatto che richiede solo sequenze invece di iteratori, il che sembra piuttosto arbitrario.

In qualsiasi modo tu lo faccia, dovrai eseguire l'intero iteratore. Alla massima efficienza, se non hai più bisogno dell'iteratore, potresti semplicemente eliminare tutti i valori:

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

Penso che questa sia una soluzione non ottimale, però.


4
reversed () non richiede un iteratore, solo sequenze.
Thomas Wouters,

3
Non è affatto arbitrario. L'unico modo per invertire un iteratore è iterare fino alla fine, mantenendo tutti gli elementi in memoria. Io, e, devi prima crearne una sequenza, prima di poterla invertire. Il che ovviamente sconfigge lo scopo dell'iteratore in primo luogo, e significherebbe anche che improvvisamente usi molta memoria senza una ragione apparente. Quindi è l'opposto di arbitrario, in effetti. :)
Lennart Regebro

@ Lennart - Quando ho detto arbitrario, intendevo fastidioso. Sto concentrando le mie abilità linguistiche sulla mia carta che dovrebbe arrivare tra poche ore a quest'ora del mattino.
Chris Lutz

3
Giusto. Sebbene IMO sarebbe più fastidioso se accettasse gli iteratori, perché quasi ogni utilizzo sarebbe una cattiva idea (tm). :)
Lennart Regebro

3

La libreria toolz fornisce una buona soluzione:

from toolz.itertoolz import last
last(values)

Ma l'aggiunta di una dipendenza non core potrebbe non valere la pena utilizzarla solo in questo caso.



0

Vorrei solo usare next(reversed(myiter))


8
TypeError: l'argomento di reversed () deve essere una sequenza
Labo

0

La domanda riguarda il recupero dell'ultimo elemento di un iteratore, ma se il tuo iteratore viene creato applicando condizioni a una sequenza, allora invertito può essere utilizzato per trovare il "primo" di una sequenza invertita, guardando solo gli elementi necessari, applicando invertire la sequenza stessa.

Un esempio artificioso,

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

0

In alternativa per infiniti iteratori puoi usare:

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

Pensavo che sarebbe stato più lento allora dequema è altrettanto veloce ed è in realtà più veloce del metodo loop (in qualche modo)


-6

La domanda è sbagliata e può solo portare a una risposta complicata e inefficiente. Per ottenere un iteratore, ovviamente parti da qualcosa che è iterabile, che nella maggior parte dei casi offrirà un modo più diretto per accedere all'ultimo elemento.

Una volta creato un iteratore da un iterabile, sei bloccato nel passare attraverso gli elementi, perché questa è l'unica cosa che fornisce un iterabile.

Quindi, il modo più efficiente e chiaro non è creare l'iteratore in primo luogo, ma utilizzare i metodi di accesso nativi dell'iterabile.


5
Quindi come si ottiene l'ultima riga di un file?
Brice M. Dempsey

@ BriceM.Dempsey Il modo migliore non è iterare l'intero file (forse enorme) ma andando alla dimensione del file meno 100, leggi gli ultimi 100 byte, cerca una nuova riga in essi, se non ce n'è, vai indietro di altri 100 byte, ecc. Puoi anche aumentare la dimensione del passo indietro, a seconda del tuo scenario. Leggere un miliardo di righe è sicuramente una soluzione non ottimale.
Alfe
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.