Каков питонический способ обнаружения последнего элемента в цикле python 'for'?

StackOverflow https://stackoverflow.com/questions/1630320

Вопрос

Я хотел бы знать лучший способ (более компактный и "питонический" способ) выполнить специальную обработку для последнего элемента в цикле 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 особым случаем вместо последнего:

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 & # 8220; с особым случаем & # 8221;. <Ч>

Используя тот же принцип, но более компактный:

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 .

У вас есть элемент, за которым следует последовательность пар (между элементами). Вы также можете просмотреть это как последовательность пар (элемент, между), за которыми следует элемент. Как правило, проще воспринимать первый элемент как особенный, а все остальные как «стандартный». случай.

Кроме того, чтобы избежать повторения кода, вы должны предоставить функцию или другой объект, содержащий код, который вы не хотите повторять. Внедрение оператора 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 , вы можете просто использовать обозначение:

L[-1]

Тем не менее, похоже, что вы делаете больше, чем это. В твоем пути нет ничего плохого. Я даже быстро взглянул на код Django для их шаблоны тегов, и они делают в основном то, что вы делаете.

Хотя этот вопрос довольно старый, я пришел сюда через Google и нашел довольно простой способ: нарезка списка. Допустим, вы хотите поставить '& amp;' между всеми записями списка.

s = ""
l = [1, 2, 3]
for i in l[:-1]:
    s = s + str(i) + ' & '
s = s + str(l[-1])

Это возвращает '1 & amp; 2 & amp; 3' .

Это похоже на подход Антса Аасмы, но без использования модуля itertools. Это также запаздывающий итератор, который просматривает один элемент в потоке итератора:

def last_iter(it):
    # Ensure it's 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

Нет ли возможности перебрать все элементы, кроме последнего, и обработать последний вне цикла? В конце концов, создается цикл, который делает что-то похожее на все элементы, над которыми вы зацикливаетесь; если одному элементу нужно что-то особенное, его не должно быть в цикле.

(см. также этот вопрос: делает -The-последний элемент-в-петля-заслуживает-а-раздельного лечение )

РЕДАКТИРОВАТЬ: поскольку вопрос больше касается вопроса "между", либо элемент first является особым в том смысле, что у него нет предшественника, либо элемент last Элемент отличается тем, что у него нет преемника.

В вашем способе нет ничего плохого, если только у вас не будет 100 000 циклов и вы не захотите сохранить 100 000 операторов "if".В таком случае, вы можете пойти этим путем :

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

Результаты :

1
2
3
3

Но на самом деле, в твоем случае я чувствую, что это перебор.

В любом случае, вам, вероятно, повезет больше с нарезкой :

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

В конце концов, это отличный способ сделать что-то для вас, и это будет работать с любым iterable, включая те, без которых __len__ :

item = ''
for item in iterable :
    print item
print item

Выходы:

1
2
3
3

Если я чувствую, что поступил бы именно так, мне это кажется простым.

Используйте нарезку и is , чтобы проверить последний элемент:

for data in data_list:
    <code_that_is_done_for_every_element>
    if not data is data_list[-1]:
        <code_that_is_done_between_elements>

Caveat emptor : это работает только в том случае, если все элементы в списке на самом деле разные (имеют разные места в памяти). Под капотом Python может обнаружить одинаковые элементы и использовать для них одни и те же объекты. Например, для строк с одинаковым значением и общими целыми числами.

Google привел меня к этому старому вопросу, и я думаю, что мог бы добавить другой подход к этой проблеме.

Большинство ответов здесь будут касаться правильной обработки элемента управления цикла for, как его просили, но если data_list разрушаем, я бы посоветовал вам выталкивать элементы из списка до тех пор, пока вы не получите пустой список

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()

вы даже можете использовать while 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 было вызвано, когда вы этого хотите.

Также держите `counstruct

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])

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

Там может быть несколько способов. нарезка будет самой быстрой. Добавление еще одного, который использует метод .index ():

>>> l1 = [1,5,2,3,5,1,7,43]                                                 
>>> [i for i in l1 if l1.index(i)+1==len(l1)]                               
[43]
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top