Самый чистый способ получить последний элемент из итератора Python


110

Как лучше всего получить последний элемент из итератора в Python 2.6? Например, скажите

my_iter = iter(range(5))

Какой самый короткий код / ​​самый чистый способ получить 4 от my_iter?

Я мог бы это сделать, но это кажется не очень эффективным:

[x for x in my_iter][-1]

4
Итераторы предполагают, что вы хотите перебирать элементы, а не обращаться к последним элементам. Что мешает вам просто использовать range (5) [- 1]?
Фрэнк

7
@Frank - я предположил, что реальный итератор был более сложным и / или более удаленным, и / или более сложным для управления, чемiter(range(5))
Крис Лутц

3
@Frank: тот факт, что на самом деле это гораздо более сложная функция генератора, которая снабжает итератор. Я просто придумал этот пример, чтобы было просто и понятно, что происходит.
Питер

4
Если вам нужен последний элемент итератора, есть большая вероятность, что вы делаете что-то не так. Но ответ в том, что на самом деле нет более чистого способа перебора итератора. Это связано с тем, что итераторы не имеют размера и, по сути, могут вообще никогда не заканчиваться, и поэтому могут не иметь последнего элемента. (Это, конечно, означает, что ваш код будет работать вечно). Итак, остается нерешенным вопрос: зачем вам нужен последний элемент итератора?
Леннарт Регебро

3
@Peter: Обновите свой вопрос, пожалуйста. Не добавляйте кучу комментариев к собственному вопросу. Обновите вопрос и удалите комментарии.
S.Lott

Ответы:


100
item = defaultvalue
for item in my_iter:
    pass

4
Почему заполнитель «значение по умолчанию»? А почему бы и нет None? Именно для этого и нужно None. Вы предполагаете, что какое-то значение по умолчанию для конкретной функции может быть правильным? Если итератор на самом деле не выполняет итерацию, то значение вне диапазона более значимо, чем какое-то вводящее в заблуждение значение по умолчанию для конкретной функции.
S.Lott

46
Значение по умолчанию - это просто заполнитель для моего примера. Если вы хотите использовать Noneзначение по умолчанию, это ваш выбор. Ни один не всегда является наиболее разумным значением по умолчанию и может даже не выходить за рамки допустимого. Лично я предпочитаю использовать defaultvalue = object (), чтобы убедиться, что это действительно уникальное значение. Я просто указываю, что выбор значения по умолчанию выходит за рамки этого примера.
Thomas Wouters

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

8
Есть ошибка дизайна во всех итераторах всех типов встроенных контейнеров? Впервые слышу об этом :)
Томас Воутерс

7
Хотя это, вероятно, более быстрое решение, оно полагается на утечку переменной в циклах for (функция для некоторых, ошибка для других - вероятно, 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.
Sven Marnach

11
+1 за техническую правильность, но читатели должны иметь обычные предостережения Python: «Вам ДЕЙСТВИТЕЛЬНО нужно это оптимизировать?», «Это менее явно, что не является Pythonic» и «Более высокая скорость зависит от реализации, которая может поменяться."
leewz

1
Кроме того, это боров памяти
Eelco Hoogendoorn

6
@EelcoHoogendoorn Почему у него слишком много памяти, даже если maxlen равен 1?
Крис Весселинг,

1
Из всех представленных здесь решений я считаю это самым быстрым и эффективным с точки зрения памяти .
Маркус Штраус

67

Если вы используете 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, которую вы показали. Я просто хотел прояснить, что это не работает для «больших данных». Для этого я использую collections.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), а нас интересует только value. Интересная идея.
Taylor

21

Маловероятно, что это будет быстрее, чем пустой цикл for из-за лямбда, но, возможно, это даст кому-то идею

reduce(lambda x,y:y,my_iter)

Если 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: некоторые функции возвращают только итератор. Это короткий и довольно читаемый способ сделать это в этом случае (для неэпических списков).
serv-inc

Это наименее эффективный способ избежать вредной вредной привычки. Другой - использовать sort (sequence) [- 1], чтобы получить максимальный элемент последовательности. Пожалуйста, никогда не используйте эти дурные паттерны, если вы любите работать программистом.
Максим Ганенко

5

я хотел бы использовать reversed , за исключением того, что он принимает только последовательности вместо итераторов, что кажется довольно произвольным.

Как бы то ни было, вам придется пройти через весь итератор. При максимальной эффективности, если итератор больше не понадобится, можно просто выбросить все значения:

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

Я считаю, что это неоптимальное решение.


4
reversed () не использует итератор, только последовательности.
Thomas Wouters

3
Это вовсе не произвольно. Единственный способ отменить итератор - выполнить итерацию до конца, сохраняя при этом все элементы в памяти. Я, е, вам нужно сначала составить из него последовательность, прежде чем вы сможете ее отменить. Что, конечно, в первую очередь противоречит цели итератора, а также означает, что вы внезапно израсходовали много памяти без видимой причины. На самом деле это противоположность произволу. :)
Леннарт Регебро

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

3
Справедливо. Хотя ИМО было бы более раздражающим, если бы он действительно принимал итераторы, потому что почти любое его использование было бы плохой идеей (tm). :)
Леннарт Регебро

3

Библиотека toolz предлагает хорошее решение:

from toolz.itertoolz import last
last(values)

Но добавление неосновной зависимости может не стоить того, чтобы использовать ее только в этом случае.



0

Я бы просто использовал next(reversed(myiter))


8
TypeError: аргумент для reversed () должен быть последовательностью
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 байт и т. д. Вы также можете увеличить размер шага назад, в зависимости от вашего сценария. Определенно, читать миллиард строк - неоптимальное решение.
Alfe
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.