Najczystszy sposób na uzyskanie ostatniego elementu z iteratora Pythona


110

Jaki jest najlepszy sposób na uzyskanie ostatniego elementu z iteratora w Pythonie 2.6? Na przykład powiedz

my_iter = iter(range(5))

Jaka jest najkrótsza-code / najczystszym sposobem na uzyskanie 4od my_iter?

Mógłbym to zrobić, ale nie wydaje się to zbyt wydajne:

[x for x in my_iter][-1]

4
Iteratory zakładają, że chcesz iterować po elementach i tak naprawdę nie uzyskać dostępu do ostatnich elementów. Co powstrzymuje Cię przed zwykłym użyciem range (5) [- 1]?
Frank

7
@Frank - Zakładałem, że rzeczywisty iterator jest bardziej złożony i / lub dalej i / lub trudniejszy do kontrolowania niżiter(range(5))
Chris Lutz

3
@Frank: fakt, że w rzeczywistości jest to znacznie bardziej skomplikowana funkcja generatora, która dostarcza iterator. Właśnie stworzyłem ten przykład, aby był prosty i jasny, co się dzieje.
Peter

4
Jeśli potrzebujesz ostatniego elementu iteratora, istnieje duża szansa, że ​​robisz coś źle. Ale odpowiedź jest taka, że ​​tak naprawdę nie ma czystszego sposobu na iterację przez iterator. Dzieje się tak, ponieważ iteratory nie mają rozmiaru i mogą w ogóle nie kończyć się w ogóle i jako takie mogą nie mieć ostatniego elementu. (Oznacza to, że twój kod będzie oczywiście działał wiecznie). A więc pytanie brzmi: dlaczego potrzebujesz ostatniego elementu iteratora?
Lennart Regebro

3
@Peter: Zaktualizuj swoje pytanie. Nie dodawaj wielu komentarzy do swojego pytania. Zaktualizuj pytanie i usuń komentarze.
S.Lott

Odpowiedzi:


100
item = defaultvalue
for item in my_iter:
    pass

4
Dlaczego zmienna „defaultvalue”? Dlaczego nie None? Właśnie po to Nonejest. Czy sugerujesz, że niektóre wartości domyślne specyficzne dla funkcji mogą być nawet poprawne? Jeśli iterator w rzeczywistości nie iteruje, to wartość poza pasmem jest bardziej znacząca niż jakaś myląca wartość domyślna specyficzna dla funkcji.
S.Lott

46
Wartość domyślna jest tylko symbolem zastępczym dla mojego przykładu. Jeśli chcesz użyć Nonewartości domyślnej, to Twój wybór. Brak nie zawsze jest najbardziej rozsądną wartością domyślną i może nawet nie być poza pasmem. Osobiście używam „defaultvalue = object ()”, aby upewnić się, że jest to naprawdę wyjątkowa wartość. Wskazuję tylko, że wybór wartości domyślnej jest poza zakresem tego przykładu.
Thomas Wouters

28
@ S.Lott: być może warto rozróżnić różnicę między pustym iteratorem a iteratorem, który ma Noneostateczną wartość
John La Rooy

8
Wystąpił błąd projektowy we wszystkich iteratorach wszystkich wbudowanych typów kontenerów? Pierwszy raz o tym słyszałem :)
Thomas Wouters

7
Chociaż jest to prawdopodobnie szybsze rozwiązanie, polega na przecieku zmiennej w pętlach for (funkcja dla niektórych, błąd dla innych - prawdopodobnie faceci z FP są przerażeni). W każdym razie Guido powiedział, że to zawsze będzie działać w ten sposób, więc jest to bezpieczna konstrukcja.
tokland

68

Użyj dequerozmiaru 1.

from collections import deque

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

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

6
W rzeczywistości jest to najszybszy sposób na wyczerpanie długiej sekwencji, choć tylko trochę szybciej niż pętla for.
Sven Marnach

11
+1 za poprawność techniczną, ale czytelnicy powinni mieć typowe zastrzeżenia Pythona: „Czy NAPRAWDĘ potrzebujesz to zoptymalizować?”, „To jest mniej wyraźne, co nie jest Pythonem” i „Szybsza prędkość zależy od implementacji, która może zmienić."
leewz

1
jest to również świnia pamięci
Eelco Hoogendoorn

6
@EelcoHoogendoorn Dlaczego jest to świnia pamięci, nawet z maxlen 1?
Chris Wesseling,

1
Ze wszystkich przedstawionych dotychczas rozwiązań uważam, że jest to najszybsze i najbardziej wydajne pod względem pamięci .
Markus Strauss

66

Jeśli używasz Pythona 3.x:

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

jeśli używasz Pythona 2.7:

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


Dygresja:

Zwykle rozwiązanie przedstawione powyżej jest tym, czego potrzebujesz w zwykłych przypadkach, ale jeśli masz do czynienia z dużą ilością danych, bardziej wydajne jest użycie dequerozmiaru 1. ( źródło )

from collections import deque

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

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

1
@virtualxtc: podkreślenie to tylko identyfikator. Gwiazda z przodu mówi „rozwiń listę”. Bardziej czytelne byłoby *lst, last = some_iterable.
pepr

4
@virtualxtc nope _to specjalna zmienna w Pythonie, używana albo do przechowywania ostatniej wartości, albo do stwierdzenia, że ​​nie obchodzi mnie wartość, więc można ją wyczyścić.
DhiaTN

1
To rozwiązanie Python 3 nie jest wydajne pod względem pamięci.
Markus Strauss

3
@DhiaTN Tak, masz całkowitą rację. Właściwie podoba mi się idiom Pythona 3, który pokazałeś bardzo. Chciałem tylko wyjaśnić, że to nie działa dla „dużych zbiorów danych”. Używam do tego collections.deque, co jest szybkie i wydajne pod względem pamięci (zobacz rozwiązanie z martin23487234).
Markus Strauss

1
Ten przykład py3,5 + powinien być w PEP 448. Cudownie.
EliadL,

33

Prawdopodobnie warto go użyć, __reversed__jeśli jest dostępny

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

27

Tak prosty jak:

max(enumerate(the_iter))[1]

8
Och, to jest sprytne. Nie jest to najbardziej wydajne ani czytelne, ale sprytne.
timgeb

6
Więc po prostu głośno myślę ... To działa, ponieważ enumeratezwraca (index, value)takie jak: (0, val0), (1, val1), (2, val2)... a następnie domyślnie, maxgdy otrzyma listę krotek, porównuje tylko z pierwszą wartością krotki, chyba że dwie pierwsze wartości są równe, których nigdy tutaj nie ma ponieważ reprezentują indeksy. W takim przypadku końcowy indeks dolny jest taki, że max zwraca całą (idx, wartość) krotkę, podczas gdy jesteśmy tylko zainteresowani value. Ciekawy pomysł.
Taylor Edmiston

21

Jest mało prawdopodobne, aby było to szybsze niż pusta pętla for ze względu na lambdę, ale może da pomysł komuś innemu

reduce(lambda x,y:y,my_iter)

Jeśli iter jest pusty, zostanie zgłoszony TypeError


IMHO, ten jest najbardziej bezpośredni, koncepcyjnie. Zamiast podbijać TypeErrordla pustej iterowalnej, możesz również podać wartość domyślną poprzez wartość początkową reduce(), np last = lambda iterable, default=None: reduce(lambda _, x: x, iterable, default).
egnha,

9

Jest to

list( the_iter )[-1]

Jeśli długość iteracji jest naprawdę epicka - tak długa, że ​​zmaterializowanie listy wyczerpuje pamięć - wtedy naprawdę musisz przemyśleć projekt.


1
To najprostsze rozwiązanie.
laike9m

2
Nieco lepiej jest użyć krotki.
Christopher Smith,

9
Zdecydowanie nie zgadzam się z ostatnim zdaniem. Praca z bardzo dużymi zestawami danych (które mogłyby przekroczyć granice pamięci, gdyby zostały załadowane w całości na raz) jest głównym powodem używania iteratora zamiast listy.
Paul,

@Paul: niektóre funkcje zwracają tylko iterator. Jest to krótki i całkiem czytelny sposób na zrobienie tego w takim przypadku (w przypadku list nie-epickich).
serv-inc

To najmniej skuteczny sposób, którego należy unikać jako zły, zły, zły nawyk. Innym jest użycie sort (sequence) [- 1], aby uzyskać maksymalny element sekwencji. Nigdy nie używaj tych złych wzorów, jeśli chcesz zostać inżynierem oprogramowania.
Maksym Ganenko

5

Chciałbym użyć reversed, z wyjątkiem tego, że przyjmuje tylko sekwencje zamiast iteratorów, co wydaje się raczej arbitralne.

Jakkolwiek to zrobisz, będziesz musiał przejść przez cały iterator. Przy maksymalnej wydajności, jeśli nie będziesz już potrzebować iteratora, możesz po prostu usunąć wszystkie wartości:

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

Myślę jednak, że jest to nieoptymalne rozwiązanie.


4
reverse () nie przyjmuje iteratora, tylko sekwencje.
Thomas Wouters

3
To wcale nie jest arbitralne. Jedynym sposobem na odwrócenie iteratora jest iteracja do końca, przy jednoczesnym zachowaniu wszystkich elementów w pamięci. To znaczy, musisz najpierw zrobić z tego sekwencję, zanim będziesz mógł ją odwrócić. Co oczywiście podważa cel iteratora w pierwszej kolejności, a także oznaczałoby, że nagle zużywasz dużo pamięci bez wyraźnego powodu. W rzeczywistości jest to przeciwieństwo arbitralności. :)
Lennart Regebro

@Lennart - Kiedy powiedziałem arbitralnie, miałem na myśli irytujące. Skupiam swoje umiejętności językowe na mojej pracy, która ma się pojawić za kilka godzin o tej porze rano.
Chris Lutz,

3
Słusznie. Chociaż IMO byłoby bardziej irytujące, gdyby zaakceptował iteratory, ponieważ prawie każde jego użycie byłoby złym pomysłem (tm). :)
Lennart Regebro

3

Toolz biblioteka zapewnia miły rozwiązanie:

from toolz.itertoolz import last
last(values)

Ale dodanie innej zależności może nie być tego warte, ponieważ używa jej tylko w tym przypadku.



0

Po prostu użyłbym next(reversed(myiter))


8
TypeError: argument do reverse () musi być sekwencją
Labo

0

Pytanie dotyczy uzyskania ostatniego elementu iteratora, ale jeśli twój iterator jest tworzony przez zastosowanie warunków do sekwencji, to odwrócenie może być użyte do znalezienia "pierwszego" odwróconej sekwencji, patrząc tylko na potrzebne elementy, stosując odwrócić do samej sekwencji.

Wymyślony przykład,

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

0

Alternatywnie dla nieskończonych iteratorów możesz użyć:

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

Myślałem, że wtedy będzie wolniej, dequeale jest tak samo szybki i faktycznie szybszy niż dla metody pętli (jakoś)


-6

Pytanie jest błędne i może prowadzić tylko do skomplikowanej i nieskutecznej odpowiedzi. Aby otrzymać iterator, oczywiście zaczynasz od czegoś, co jest iterowalne, co w większości przypadków oferuje bardziej bezpośredni sposób dostępu do ostatniego elementu.

Po utworzeniu iteratora z iterowalnego utkniesz w przejściu przez elementy, ponieważ jest to jedyna rzecz, jaką zapewnia iteracja.

Zatem najbardziej wydajnym i przejrzystym sposobem nie jest utworzenie iteratora w pierwszej kolejności, ale użycie natywnych metod dostępu iterowalnego.


5
Jak więc uzyskać ostatnią linię pliku?
Brice M. Dempsey,

@ BriceM.Dempsey Najlepszym sposobem nie jest iterowanie całego (może dużego) pliku, ale przejście do rozmiaru pliku minus 100, odczytanie ostatnich 100 bajtów, skanowanie w poszukiwaniu nowej linii, jeśli nie ma, przejdź cofnij o kolejne 100 bajtów itd. Możesz także zwiększyć rozmiar cofania, w zależności od scenariusza. Czytanie gazillionów to zdecydowanie nieoptymalne rozwiązanie.
Alfe
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.