Как питонный способ обнаружить последний элемент в цикле for?
Я хотел бы знать лучший способ (более компактный и “питонический” способ) сделать специальную обработку для последнего элемента в цикле for. Существует фрагмент кода, который следует вызывать только между элементами, подавляя в последнем.
Вот как я сейчас это делаю:
for i, data in enumerate(data_list): code_that_is_done_for_every_element if i != len(data_list) - 1: code_that_is_done_between_elements
Примечание: я не хочу делать это с помощью таких хаков, как использование reduce . 😉
В большинстве случаев проще (и дешевле) сделать первую итерацию специальным случаем, а не последним:
first = True for data in data_list: if first: first = False else: between_items() item()
Это будет работать для любой итерации, даже для тех, у которых нет len() :
file = open('/path/to/file') for line in file: process_line(line) # No way of telling if this is the last line!
Кроме того, я не думаю, что есть общее превосходное решение, поскольку оно зависит от того, что вы пытаетесь сделать. Например, если вы строите строку из списка, естественно, лучше использовать str.join() , чем использовать цикл for со специальным случаем “.
Используя тот же принцип, но более компактный:
for i, line in enumerate(data_list): if i > 0: between_items() item()
Выглядит знакомо, не так ли?:)
Для @ofko и других, которым действительно нужно выяснить, является ли текущее значение итерабельности без len() последним, вам нужно будет заглянуть в будущее:
def lookahead(iterable): """Pass through all values from the given iterable, augmented by the information if there are more values to come after the current one (True), or if it is the last value (False). """ # Get an iterator and pull the first value. it = iter(iterable) last = next(it) # Run the iterator to exhaustion (starting from the second value). for val in it: # Report the *previous* value (more to come). yield last, True last = val # Report the last value. yield last, False
Затем вы можете использовать его следующим образом:
>>> for i, has_more in lookahead(range(3)): . print(i, has_more) 0 True 1 True 2 False
“Код между” – это пример шаблона Head-Tail.
У вас есть элемент, за которым следует последовательность (между, item) парами. Вы также можете просмотреть это как последовательность пар (item, between), за которой следует элемент. Как правило, проще сделать первый элемент как особый, а все остальные – “стандартным”.
Кроме того, чтобы избежать повторения кода, вы должны предоставить функцию или другой объект, чтобы содержать код, который вы не хотите повторять. Внедрение оператора if в цикле, который всегда является ложным, за исключением одного раза глупо.
def item_processing( item ): # *the common processing* head_tail_iter = iter( someSequence ) head = head_tail_iter.next() item_processing( head ) for item in head_tail_iter: # *the between processing* item_processing( item )
Это более надежно, потому что его немного легче доказать, он не создает дополнительную структуру данных (т.е. копию списка) и не требует много потраченного впустую выполнения if условие, которое всегда ложно, за исключением одного раза.
Если вы просто хотите изменить последний элемент в data_list , вы можете просто использовать обозначение:
Однако похоже, что вы делаете больше, чем это. В твоем пути нет ничего плохого. Я даже быстро взглянул на некоторый код Django для своих тегов шаблонов, и они в основном делают то, что вы делаете.
Хотя этот вопрос довольно старый, я пришел сюда через google, и я нашел довольно простой способ: Срезать список. Скажем, вы хотите поставить ‘&’ между всеми элементами списка.
s = "" l = [1, 2, 3] for i in l[:-1]: s = s + str(i) + ' & ' s = s + str(l[-1])
Это похоже на подход Ants Aasma, но без использования модуля itertools. Он также представляет собой отстающий итератор, который ищет один элемент в потоке итератора:
def last_iter(it): # Ensure it an iterator and get the first field it = iter(it) prev = next(it) for item in it: # Lag by one item so I know I'm not at the end yield 0, prev prev = item # Last item yield 1, prev def test(data): result = list(last_iter(data)) if not result: return if len(result) > 1: assert set(x[0] for x in result[:-1]) == set([0]), result assert result[-1][0] == 1 test([]) test([1]) test([1, 2]) test(range(5)) test(xrange(4)) for is_last, item in last_iter("Hi!"): print is_last, item
for x in list: #code if x == list[-1]: #code
pos = -1 for x in list: pos += 1 #code if pos == len(list) - 1: #code for x in list: #code #code - e.g. print x if len(list) > 0: for x in list[:-1] #code for x in list[-1]: #code
Вы можете использовать скользящее окно над входными данными, чтобы получить заглядывание при следующем значении и использовать часовое для определения последнего значения. Это работает на любом истребителе, поэтому вам не нужно знать длину заранее. Попарная реализация выполняется из рецептов itertools.
from itertools import tee, izip, chain def pairwise(seq): a,b = tee(seq) next(b, None) return izip(a,b) def annotated_last(seq): """Returns an iterable of pairs of input item and a boolean that show if the current item is the last item in the sequence.""" MISSING = object() for current_item, next_item in pairwise(chain(seq, [MISSING])): yield current_item, next_item is MISSING: for item, is_last_item in annotated_last(data_list): if is_last_item: # current item is the last item
Нет ли возможности перебирать все, но последний элемент, и обрабатывать последний за пределами цикла? В конце концов, цикл создается, чтобы сделать что-то похожее на все элементы, которые вы зацикливаете; если один элемент нуждается в чем-то особенном, он не должен находиться в цикле.
EDIT: поскольку вопрос больше связан с “промежутком”, либо первый элемент является особенным, поскольку он не имеет предшественника, либо последний элемент является особенным, поскольку он не имеет преемника.
Нет ничего плохого в вашем пути, если у вас не будет 100 000 циклов и вы хотите сохранить 100 000 слов “если”. В этом случае вы можете пойти следующим образом:
iterable = [1,2,3] # Your date iterator = iter(iterable) # get the data iterator try : # wrap all in a try / except while 1 : item = iterator.next() print item # put the "for loop" code here except StopIteration, e : # make the process on the last element here print item
Но на самом деле, в вашем случае я чувствую, что это излишне.
В любом случае вам, вероятно, повезет с нарезкой:
for item in iterable[:-1] : print item print "last :", iterable[-1] #outputs 1 2 last : 3
for item in iterable : print item print iterable[-1] #outputs 1 2 3 last : 3
В конце концов, способ KISS для вас, и это будет работать с любым итерабельным, в том числе без __len__ :
item = '' for item in iterable : print item print item
Если мне кажется, что я сделаю так, мне кажется просто.
Используйте срез и is , чтобы проверить последний элемент:
for data in data_list: if not data is data_list[-1]:
Caveat emptor. Это работает только в том случае, если все элементы в списке действительно разные (имеют разные места в памяти). Под капотом Python может обнаруживать равные элементы и повторно использовать для них одни и те же объекты. Например, для строк одного и того же значения и общих целых чисел.
Google привлек меня к этому старому вопросу, и я думаю, что я мог бы добавить другой подход к этой проблеме.
В большинстве ответов здесь будет рассмотрена правильная обработка управления контуром цикла, как это было задано, но если файл данных является разрушаемым, я бы предложил вам вытащить элементы из списка, пока вы не получите пустой список
while True: element = element_list.pop(0) do_this_for_all_elements() if not element: do_this_only_for_last_element() break do_this_for_all_elements_but_last()
вы даже можете использовать len (element_list), если вам не нужно ничего делать с последним элементом. Я нахожу это решение более элегантным, чем обращение к next().
Мне нравится подход @ethan-t, но while True опасен с моей точки зрения.
while L: e = L.pop(0) # process element if not L: print('Last element has been detected.')
Задержка специальной обработки последнего элемента до тех пор, пока цикл не будет завершен.
>>> for i in (1, 2, 3): . pass . >>> i 3
Предполагая ввод как итератор, здесь используется метод tee и izip из itertools:
from itertools import tee, izip items, between = tee(input_iterator, 2) # Input must be an iterator. first = items.next() do_to_every_item(first) # All "do to every" operations done to first item go here. for i, b in izip(items, between): do_between_items(b) # All "between" operations go here. do_to_every_item(i) # All "do to every" operations go here.
>>> def do_every(x): print "E", x . >>> def do_between(x): print "B", x . >>> test_input = iter(range(5)) >>> >>> from itertools import tee, izip >>> >>> items, between = tee(test_input, 2) >>> first = items.next() >>> do_every(first) E 0 >>> for i,b in izip(items, between): . do_between(b) . do_every(i) . B 0 E 1 B 1 E 2 B 2 E 3 B 3 E 4 >>>
если вы просматриваете список, для меня это тоже сработало:
for j in range(0, len(Array)): if len(Array) - j > 1: notLast()
Самое простое решение, которое приходит мне на ум:
for item in data_list: try: print(new) except NameError: pass new = item print('The last item: ' + str(new))
Поэтому мы всегда смотрим вперед на один элемент, задерживая обработку одной итерацией. Чтобы пропустить что-то во время первой итерации, я просто поймаю ошибку.
Конечно, вам нужно немного подумать, чтобы NameError был поднят, когда вы этого захотите.
Также сохраняйте `advstruct
try: new except NameError: pass else: # continue here if no error was raised
Это полагается, что имя new ранее не было определено. Если вы параноик, вы можете убедиться, что new не существует, используя:
try: del new except NameError: pass
В качестве альтернативы вы также можете использовать оператор if ( if notfirst: print(new) else: notfirst = True ). Но насколько я знаю, накладные расходы больше.
Using `timeit` yields: . try: new = 'test' . except NameError: pass . 100000000 loops, best of 3: 16.2 ns per loop
поэтому я ожидаю, что накладные расходы не поддаются выбору.
Подсчитайте элементы один раз и не отставайте от оставшихся элементов:
remaining = len(data_list) for data in data_list: code_that_is_done_for_every_element remaining -= 1 if remaining: code_that_is_done_between_elements
Таким образом вы только оцениваете длину списка один раз. Многие из решений на этой странице, похоже, предполагают, что длина недоступна заранее, но это не является частью вашего вопроса. Если у вас есть длина, используйте его.
Для меня самый простой и питонный способ обработки особого случая в конце списка:
for data in data_list[:-1]: handle_element(data) handle_special_element(data_list[-1])
Конечно, это также может быть использовано для особой обработки первого элемента.
some_list = ['gfg', 'fdsfsd', 'dasda'] for i in some_list: if i == somelist[-1]: make_last_shet_here else: make ordinary stuff
Там может быть несколько способов. нарезка будет самой быстрой. Добавляем еще один, который использует метод .index():
>>> l1 = [1,5,2,3,5,1,7,43] >>> [i for i in l1 if l1.index(i)+1==len(l1)] [43]
Вместо того, чтобы считать, вы также можете считать:
nrToProcess = len(list) for s in list: s.doStuff() nrToProcess -= 1 if nrToProcess==0: # this is the last one s.doSpecialStuff()