Найпростіший спосіб отримати останній елемент з ітератора Python


110

Який найкращий спосіб отримати останній елемент з ітератора в Python 2.6? Наприклад, скажіть

my_iter = iter(range(5))

Що є найкоротшим-код / чистий спосіб отримання 4від my_iter?

Я міг би це зробити, але це не дуже ефективно:

[x for x in my_iter][-1]

4
Ітератори припускають, що ви хочете переглядати елементи, а не отримувати доступ до останніх елементів. Що вас заважає просто використовувати діапазон (5) [- 1]?
Френк

7
@Frank - Я припускав, що фактичний ітератор був складнішим та / або далі та / або важче керувати, ніжiter(range(5))
Кріс Лутц,

3
@Frank: той факт, що насправді набагато складніша функція генератора, яка постачає ітератор. Я просто склав цей приклад, щоб було просто і зрозуміло, що відбувається.
Пітер

4
Якщо ви хочете останній елемент ітератора, є велика ймовірність, що ви щось зробите не так. Але відповідь полягає в тому, що насправді не існує більш чистого способу, який би повторювався через ітератор. Це тому, що ітератори не мають розміру, і насправді вони взагалі не можуть закінчитися, і як такий може не бути останнього елемента. (Значить, звичайно, ваш код буде працювати назавжди). Отже, затяжне питання: Чому вам потрібен останній елемент ітератора?
Леннарт Регебро

3
@Peter: оновіть своє запитання, будь ласка. Не додайте купу коментарів до питання, яким ви володієте. Оновіть питання та видаліть коментарі.
С.Лотт

Відповіді:


100
item = defaultvalue
for item in my_iter:
    pass

4
Чому заповнювач "значення за замовчуванням"? Чому ні None? Це саме те None, для чого. Ви припускаєте, що якесь значення функції за замовчуванням може бути навіть правильним? Якщо ітератор насправді не повторюється, значення поза діапазону є більш значущим, ніж деякий оманливий за замовчуванням функціонал.
С.Лотт

46
Значення за замовчуванням - це лише заповнювач мій приклад. Якщо ви хочете використовувати Noneяк значення за замовчуванням, це ваш вибір. Жоден не завжди є найрозумнішим за замовчуванням і може навіть не виходити з діапазону. Особисто я схильний використовувати 'defaultvalue = object ()', щоб переконатися, що це справді унікальне значення. Я просто вказую, що вибір за замовчуванням виходить за межі цього прикладу.
Томас Вутерс

28
@ S.Lott: можливо, корисно розрізнити різницю між порожнім ітератором та ітератором, який має Noneостаточне значення
Джон Ла Рой,

8
У всіх ітераторах усіх типів вбудованих контейнерів є помилка дизайну? Вперше я чув про це :)
Thomas Wouters

7
Хоча це, мабуть, швидше рішення, воно покладається на змінну, що протікає у for-loops (функція для одних, помилка для інших - ймовірно, FP-хлопці здивовані). Як би там не було, Гуїдо сказав, що це завжди працюватиме так, тому це безпечна конструкція.
tokland

68

Використовуйте dequeрозмір 1.

from collections import deque

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

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

6
Це насправді найшвидший спосіб вичерпати довгу послідовність, хоча лише малий швидкість, ніж цикл for.
Свен Марнах

11
+1 за технічну коректність, але читачі повинні мати звичні застереження Python: "Чи дійсно вам потрібно це оптимізувати?", "Це менш явно, що не є пітонічним" та "чим швидше залежить швидкість реалізації, яка може змінитися ".
leewz

1
також, це
копита

6
@EelcoHoogendoorn Чому це кабан на пам’ять, навіть максимум 1?
Кріс Вессілінг

1
З усіх представлених тут рішень я вважаю, що це найшвидший і найефективніший з пам’яті .
Маркус Стросс

66

Якщо ви використовуєте Python 3.x:

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

якщо ви використовуєте python 2.7:

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


Бічна примітка:

Зазвичай рішення, представлене вище, - це те, що потрібно для звичайних випадків, але якщо ви маєте справу з великою кількістю даних, то ефективніше використовувати dequeрозмір 1. ( джерело )

from collections import deque

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

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

1
@virtualxtc: Підкреслення - це лише ідентифікатор. Зірка попереду каже "розширити список". Чим легше було б читати *lst, last = some_iterable.
pepr

4
@virtualxtc nope _- спеціальна змінна в python і використовується як для зберігання останнього значення, так і для того, щоб сказати, що я не дбаю про значення, щоб його можна було очистити.
DhiaTN

1
Це рішення Python 3 не є ефективною для пам'яті.
Маркус Стросс

3
@DhiaTN Так, ви абсолютно праві. Насправді мені подобається ідіома Python 3, яку ти багато показав. Я просто хотів уточнити, що це не працює для "великих даних". Для цього я використовую collection.deque, який, як виявляється, швидко і ефективно працює з пам’яттю (див. Рішення з martin23487234).
Маркус Штраус

1
Цей приклад py3.5 + має бути в PEP 448. Чудово.
EliadL

33

Можливо, варто скористатися, __reversed__якщо вона є в наявності

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

27

Настільки ж просто, як:

max(enumerate(the_iter))[1]

8
О, це розумно. Не найефективніший чи легкий для читання, але розумний.
timgeb

6
Тож просто розмірковуючи вголос ... Це працює, тому що enumerateповертається (index, value)як: (0, val0), (1, val1), (2, val2)..., а потім за замовчуванням, maxколи дається список кортежів, порівнюється лише з першим значенням кортежу, якщо тільки два перших значення не рівні, яких вони ніколи тут немає тому що вони представляють індекси. Тоді останній індекс є тим, що max повертає весь (idx, значення) кортеж, тоді як нас тільки цікавить value. Цікава ідея.
Тейлор Едмістон

21

Це навряд чи буде швидше, ніж порожнє для циклу завдяки лямбда, але, можливо, це дасть ідею комусь іншому

reduce(lambda x,y:y,my_iter)

Якщо ітер порожній, піднімається TypeError


ІМХО, це найбільш прямо, концептуально. Замість того, щоб піднімати TypeErrorна порожній Iterable, ви можете також вказати значення за замовчуванням з допомогою початкового значення reduce(), наприклад, last = lambda iterable, default=None: reduce(lambda _, x: x, iterable, default).
egnha

9

Ось це

list( the_iter )[-1]

Якщо тривалість ітерації справді епічна - настільки довга, що матеріалізація списку вичерпає пам’ять - тоді вам дійсно потрібно переосмислити дизайн.


1
Це найпростіше рішення.
laike9m

2
М’яко краще використовувати кортеж.
Крістофер Сміт

9
Категорично не згоден з останнім реченням. Робота з дуже великими наборами даних (які можуть перевищувати межі пам’яті, якщо вони завантажені відразу) - головна причина використання ітератора замість списку.
Павло

@Paul: деякі функції повертають лише ітератор. Це короткий і досить читабельний спосіб зробити це в такому випадку (для неепічних списків).
серв-інк

Це найменш ефективний спосіб уникати як погану шкідливу звичку. Іншим є використання сортування (послідовності) [- 1], щоб отримати максимальний елемент послідовності. Будь ласка, ніколи не використовуйте ці хворі форми, якщо ви хочете бути інженером програмного забезпечення.
Максим Ганенко

5

Я б користувався reversed , за винятком того, що він бере лише послідовності замість ітераторів, що здається досить довільним.

У будь-якому випадку, вам доведеться пробігти весь ітератор. З максимальною ефективністю, якщо ітератор вам більше не потрібен, ви можете просто виправити всі значення:

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

Я думаю, що це неоптимальне рішення.


4
reversed () не приймає ітератор, а лише послідовності.
Thomas Wouters

3
Це зовсім не довільно. Єдиний спосіб повернути ітератор - це повторити його до кінця, зберігаючи всі елементи в пам'яті. Я, е, вам потрібно спершу скласти послідовність із неї, перш ніж ви зможете її змінити. Що, звичайно, перемагає призначення ітератора в першу чергу, а також означатиме, що ви раптом використовуєте багато пам'яті без видимих ​​причин. Тож насправді це навпаки довільного. :)
Леннарт Регебро

@Lennart - Коли я сказав довільно, я мав на увазі дратує. Я зосереджую свої мовні знання на моєму папері через кілька годин в цей час вранці.
Кріс Лутц

3
Досить справедливо. Хоча IMO було б більше дратувати, якби він приймав ітератори, тому що майже будь-яке його використання було б поганою ідеєю (tm). :)
Леннарт Регебро

3

Toolz бібліотека забезпечує гарне рішення:

from toolz.itertoolz import last
last(values)

Але додавання непрофільної залежності може не варто використовувати її лише в цьому випадку.


1

Дивіться у цьому коді щось подібне:

http://excamera.com/sphinx/article-islast.html

ви можете використати його, щоб забрати останній предмет за допомогою:

[(last, e) for (last, e) in islast(the_iter) if last]

1
Будь ласка, включіть код islastу свою відповідь (див. Meta.stackexchange.com/questions/8231/… ).
Крістіан Цюпіту

0

Я б просто скористався next(reversed(myiter))


8
TypeError: аргумент для зворотного () повинен бути послідовністю
Labo

0

Питання полягає в отриманні останнього елемента ітератора, але якщо ваш ітератор створений за допомогою застосування умов до послідовності, то оберненим можна скористатися, щоб знайти "перше" зворотної послідовності, лише переглядаючи потрібні елементи, застосувавши назад до самої послідовності.

Надуманий приклад,

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

0

Або для нескінченних ітераторів ви можете використовувати:

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

Я думав, що це буде повільніше, dequeале це так само швидко, і це насправді швидше, ніж для методу циклу (якось)


-6

Питання неправильне і може призвести лише до складної та неефективної відповіді. Щоб отримати ітератор, ви, звичайно, починаєте з чогось ітерабельного, що в більшості випадків пропонує більш прямий спосіб доступу до останнього елемента.

Як тільки ви створюєте ітератор з ітерабельної програми, ви застрягли в переході через елементи, тому що це єдине, що забезпечує ітерабельність.

Отже, найефективніший і зрозумілий спосіб - це не створювати в першу чергу ітератор, а використовувати вбудовані методи доступу до ітерабельного.


5
То як би ви отримали останній рядок файлу?
Бріс М. Демпсі

@ BriceM.Dempsey Найкращий спосіб полягає не в ітерації через весь (можливо, величезний) файл, а перейшовши до розміру файлу мінус 100, прочитайте останні 100 байт, скануйте в них новий рядок, якщо його немає, перейдіть назад ще 100 байт і т. д. Ви також можете збільшити свій крок назад, залежно від сценарію. Читання газільйонних рядків, безумовно, є неоптимальним рішенням.
Альфа
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.