Цикл Python, который также обращается к предыдущему и следующему значениям

90

Как я могу перебирать список объектов, получая доступ к предыдущему, текущему и следующему элементам? Как этот код C / C ++ на Python?

foo = somevalue;
previous = next = 0;

for (i=1; i<objects.length(); i++) {
    if (objects[i]==foo) {
        previous = objects[i-1];
        next = objects[i+1];
    }
}
dir01
источник
Что должно произойти, если foo находится в начале или в конце списка? В настоящее время это выйдет за пределы вашего массива.
Брайан
2
если вам нужно первое вхождение «foo», тогда сделайте «break» из блока «for» при совпадении.
фургон
Вы хотите начать итерацию с 1-го (не 0-го) элемента и закончить итерацию с предпоследнего элемента?
smci
Гарантируется ли, что это fooпроизойдет ровно один раз в списке? Если это происходит многократно, некоторые подходы здесь не сработают или найдут только первый. И если этого никогда не произойдет, другие подходы потерпят неудачу или вызовут исключения, такие как ValueError. Помогло бы дать несколько тестов.
smci
Кроме того, ваш пример представляет собой последовательность объектов, которые имеют известную длину и являются индексируемыми. Некоторые из ответов здесь обобщают итераторы, которые не всегда индексируются, не всегда имеют длину и не всегда конечны ..
smci

Ответы:

107

Это должно помочь.

foo = somevalue
previous = next_ = None
l = len(objects)
for index, obj in enumerate(objects):
    if obj == foo:
        if index > 0:
            previous = objects[index - 1]
        if index < (l - 1):
            next_ = objects[index + 1]

Вот документация по enumerateфункции.

Хэнк Гей
источник
17
Но, вероятно, лучше всего не использовать «следующий» в качестве имени переменной, поскольку это встроенная функция.
mkosmala 01
1
Отредактированная версия этого еще не логически звука: В конце цикла objи next_будет тем же объектом для последней итерации, которая может иметь непреднамеренные побочные эффекты.
TemporalWolf
В вопросе явно говорится, что OP хочет начать итерацию с 1-го (не 0-го) элемента и закончить итерацию с предпоследнего элемента. Поэтому indexследует бежать от 1 ... (l-1), 0 ... lа не как здесь, и нет необходимости в специальных if-предложениях. Кстати, есть параметр, enumerate(..., start=1)но не для end. Так что мы действительно не хотим использовать enumerate().
smci
147

До сих пор решения касались только списков, и большинство из них копирует список. По моему опыту, во многих случаях это невозможно.

Кроме того, они не учитывают тот факт, что в списке могут быть повторяющиеся элементы.

В заголовке вашего вопроса написано « Предыдущее и следующее значения внутри цикла », но если вы запустите большинство ответов здесь внутри цикла, вам придется снова перебирать весь список для каждого элемента, чтобы найти его.

Итак, я только что создал функцию, которая. с помощью itertoolsмодуля разделяет и нарезает итерируемый объект, а также генерирует кортежи с предыдущим и следующим элементами вместе. Не совсем то, что делает ваш код, но на него стоит взглянуть, потому что он, вероятно, может решить вашу проблему.

from itertools import tee, islice, chain, izip

def previous_and_next(some_iterable):
    prevs, items, nexts = tee(some_iterable, 3)
    prevs = chain([None], prevs)
    nexts = chain(islice(nexts, 1, None), [None])
    return izip(prevs, items, nexts)

Затем используйте его в цикле, и в нем будут предыдущие и следующие элементы:

mylist = ['banana', 'orange', 'apple', 'kiwi', 'tomato']

for previous, item, nxt in previous_and_next(mylist):
    print "Item is now", item, "next is", nxt, "previous is", previous

Результаты, достижения:

Item is now banana next is orange previous is None
Item is now orange next is apple previous is banana
Item is now apple next is kiwi previous is orange
Item is now kiwi next is tomato previous is apple
Item is now tomato next is None previous is kiwi

Он будет работать со списком любого размера (потому что он не копирует список) и с любыми итерациями (файлы, наборы и т. Д.). Таким образом, вы можете просто перебирать последовательность и иметь предыдущий и следующий элементы, доступные внутри цикла. Нет необходимости снова искать элемент в последовательности.

Краткое объяснение кода:

  • tee используется для эффективного создания 3 независимых итераторов по входной последовательности
  • chainсвязывает две последовательности в одну; здесь он используется для добавления одноэлементной последовательности [None]кprevs
  • isliceиспользуется для создания последовательности всех элементов, кроме первого, затем chainиспользуется для добавления Noneв конец
  • Теперь есть 3 независимых последовательности, some_iterableкоторые выглядят так:
    • prevs: None, A, B, C, D, E
    • items: A, B, C, D, E
    • nexts: B, C, D, E, None
  • finally izipиспользуется для преобразования 3 последовательностей в одну последовательность триплетов.

Обратите внимание, что izipостанавливается, когда любая входная последовательность исчерпана, поэтому последний элемент prevsбудет проигнорирован, что правильно - нет такого элемента, который был бы последним элементом prev. Мы могли бы попытаться удалить последние элементы из, prevsно izipповедение делает это избыточным

Также отметим , что tee, izip, isliceи chainисходить от itertoolsмодуля; они оперируют своими входными последовательностями на лету (лениво), что делает их эффективными и не требует одновременного хранения всей последовательности в памяти в любое время.

В python 3, он покажет ошибку при импорте izip, вы можете использовать zipвместо izip. Нет необходимости импорта zip, он не предопределены python 3- источник

носкло
источник
3
@becomingGuru: нет необходимости превращать SO в зеркало справочной документации Python. Все эти функции очень хорошо объяснены (с примерами) в официальной документации
Эли Бендерски
1
@becomingGuru: Добавил ссылку на документацию.
nosklo
6
@LakshmanPrasad У меня настроение вики, поэтому я добавил несколько пояснений :-).
Kos
7
Возможно, стоит упомянуть, что в Python 3 izipможно заменить встроенную zipфункцию ;-)
Tomasito665
1
Это хорошее решение, но оно кажется слишком сложным. См. Stackoverflow.com/a/54995234/1265955, который был вдохновлен этим.
Victoria
6

Используя понимание списка, верните 3-кортеж с текущим, предыдущим и следующим элементами:

three_tuple = [(current, 
                my_list[idx - 1] if idx >= 1 else None, 
                my_list[idx + 1] if idx < len(my_list) - 1 else None) for idx, current in enumerate(my_list)]
RYS
источник
4

Я не знаю, как это еще не произошло, поскольку он использует только встроенные функции и легко расширяется на другие смещения:

values = [1, 2, 3, 4]
offsets = [None] + values[:-1], values, values[1:] + [None]
for value in list(zip(*offsets)):
    print(value) # (previous, current, next)

(None, 1, 2)
(1, 2, 3)
(2, 3, 4)
(3, 4, None)
Эрик Чех
источник
4

Вот версия с использованием генераторов без граничных ошибок:

def trios(iterable):
    it = iter(iterable)
    try:
        prev, current = next(it), next(it)
    except StopIteration:
        return
    for next in it:
        yield prev, current, next
        prev, current = current, next

def find_prev_next(objects, foo):
    prev, next = 0, 0
    for temp_prev, current, temp_next in trios(objects):
        if current == foo:
            prev, next = temp_prev, temp_next
    return prev, next

print(find_prev_next(range(10), 1))
print(find_prev_next(range(10), 0))
print(find_prev_next(range(10), 10))
print(find_prev_next(range(0), 10))
print(find_prev_next(range(1), 10))
print(find_prev_next(range(2), 10))

Обратите внимание, что граничное поведение заключается в том, что мы никогда не ищем «foo» в первом или последнем элементе, в отличие от вашего кода. Опять же, семантика границ странная ... и ее трудно понять из вашего кода :)

мошез
источник
2

использование условных выражений для краткости для python> = 2.5

def prenext(l,v) : 
   i=l.index(v)
   return l[i-1] if i>0 else None,l[i+1] if i<len(l)-1 else None


# example
x=range(10)
prenext(x,3)
>>> (2,4)
prenext(x,0)
>>> (None,2)
prenext(x,9)
>>> (8,None)
Макапуф
источник
2

Для тех, кто ищет решение этой проблемы, а также хочет циклически повторять элементы, ниже может сработать:

from collections import deque  

foo = ['A', 'B', 'C', 'D']

def prev_and_next(input_list):
    CURRENT = input_list
    PREV = deque(input_list)
    PREV.rotate(-1)
    PREV = list(PREV)
    NEXT = deque(input_list)
    NEXT.rotate(1)
    NEXT = list(NEXT)
    return zip(PREV, CURRENT, NEXT)

for previous_, current_, next_ in prev_and_next(foo):
    print(previous_, current_, next)
skr47ch
источник
подчеркивание в последнем next_? Невозможно редактировать - «должно быть не менее 6 ...»
Xpector
Почему это в любом случае предпочтительнее простого цикла for и доступа objects[i-1], objects[i], objects[i+1]? или генератор? Мне это кажется совершенно мракобесным. Кроме того, он без необходимости использует 3-кратную память, поскольку PREV и NEXT делают копии данных.
smci
@smci Как добиться, чтобы этот i+1подход работал для последнего элемента в списке? Следующий элемент должен быть первым. Я выхожу за пределы игровой площадки.
ElectRocnic,
1

Используя генераторы, это довольно просто:

signal = ['→Signal value←']
def pniter( iter, signal=signal ):
    iA = iB = signal
    for iC in iter:
        if iB is signal:
            iB = iC
            continue
        else:
            yield iA, iB, iC
        iA = iB
        iB = iC
    iC = signal
    yield iA, iB, iC

if __name__ == '__main__':
    print('test 1:')
    for a, b, c in pniter( range( 10 )):
        print( a, b, c )
    print('\ntest 2:')
    for a, b, c in pniter([ 20, 30, 40, 50, 60, 70, 80 ]):
        print( a, b, c )
    print('\ntest 3:')
    cam = { 1: 30, 2: 40, 10: 9, -5: 36 }
    for a, b, c in pniter( cam ):
        print( a, b, c )
    for a, b, c in pniter( cam ):
        print( a, a if a is signal else cam[ a ], b, b if b is signal else cam[ b ], c, c if c is signal else cam[ c ])
    print('\ntest 4:')
    for a, b, c in pniter([ 20, 30, None, 50, 60, 70, 80 ]):
        print( a, b, c )
    print('\ntest 5:')
    for a, b, c in pniter([ 20, 30, None, 50, 60, 70, 80 ], ['sig']):
        print( a, b, c )
    print('\ntest 6:')
    for a, b, c in pniter([ 20, ['→Signal value←'], None, '→Signal value←', 60, 70, 80 ], signal ):
        print( a, b, c )

Обратите внимание, что тесты, которые включают None и то же значение, что и значение сигнала, все еще работают, потому что проверка для значения сигнала использует «is», а сигнал - это значение, которое Python не интернирует. Однако в качестве сигнала можно использовать любое значение одноэлементного маркера, что в некоторых случаях может упростить код пользователя.

Виктория
источник
8
«Это довольно просто»,
Мигель Стивенс
Не говорите «сигнал», когда имеете в виду «дозорный». Кроме того, никогда не используйте if iB is signalдля сравнения объектов на равенство, если только signal = None, и в этом случае просто пишите Noneуже напрямую . Не используйте iterв качестве имени аргумента, поскольку оно затеняет встроенный iter(). То же next. В любом случае подход генератора может быть простоyield prev, curr, next_
smci
@smci Может быть, в вашем словаре есть другие определения, чем у меня, относительно сигнала и дозорного. Я специально использовал «is», потому что я хотел протестировать конкретный элемент, а не другие элементы с равным значением, «is» - правильный оператор для этого теста. Использование iter и next shadow только для тех вещей, на которые нет других ссылок, поэтому это не проблема, но согласовано, а не лучшая практика. Вам нужно показать больше кода, чтобы предоставить контекст для вашего последнего утверждения.
Виктория
@Victoria: «sentinel [значение]» - это четко определенный программный термин, «сигнал» - нет (не говоря уже об обработке сигналов или сигналах ядра). Что касается [сравнивая вещи в Python с isвместо ==], это хорошо известной ловушки, вот несколько причин , почему вы можете уйти с ним строки, потому что вы полагаетесь на CPython интернирование строк, но даже тогда v1 = 'monkey'; v2 = 'mon'; v3 = 'key, то v1 is (v2 + v3)дает False. И если ваш код когда-либо переключится на использование объектов вместо целых чисел / строк, использование isсломается. Так что в целом вы должны использовать ==для сравнения равенство.
smci
@smci Самая сложная проблема в компьютерном программном обеспечении - это связь, неработающие сети из-за того, что разные группы людей используют разные термины. Как говорится, стандарты отличные, они есть у всех. Я полностью понимаю разницу между операторами Python == и is, и именно поэтому я решил использовать is. Если вы посмотрите за пределы своей предвзятой «терминологии и правил», вы поймете, что == позволит любому элементу, который сравнивается с равным, завершить последовательность, тогда как использование is завершится только на конкретном объекте, который используется в качестве сигнала (или часового, если хотите).
Виктория
1

Два простых решения:

  1. Если необходимо определить переменные как для предыдущего, так и для следующего значения:
alist = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five']

prev = alist[0]
curr = alist[1]

for nxt in alist[2:]:
    print(f'prev: {prev}, curr: {curr}, next: {nxt}')
    prev = curr
    curr = nxt

Output[1]:
prev: Zero, curr: One, next: Two
prev: One, curr: Two, next: Three
prev: Two, curr: Three, next: Four
prev: Three, curr: Four, next: Five
  1. Если все значения в списке должны быть пройдены переменной текущего значения:
alist = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five']

prev = None
curr = alist[0]

for nxt in alist[1:] + [None]:
    print(f'prev: {prev}, curr: {curr}, next: {nxt}')
    prev = curr
    curr = nxt

Output[2]:
prev: None, curr: Zero, next: One
prev: Zero, curr: One, next: Two
prev: One, curr: Two, next: Three
prev: Two, curr: Three, next: Four
prev: Three, curr: Four, next: Five
prev: Four, curr: Five, next: None
Серж Точилов
источник
0

Вы можете просто использовать indexв списке, чтобы найти, где somevalueнаходится, а затем получить предыдущее и следующее по мере необходимости:


def find_prev_next(elem, elements):
    previous, next = None, None
    index = elements.index(elem)
    if index > 0:
        previous = elements[index -1]
    if index < (len(elements)-1):
        next = elements[index +1]
    return previous, next


foo = 'three'
list = ['one','two','three', 'four', 'five']

previous, next = find_prev_next(foo, list)

print previous # should print 'two'
print next # should print 'four'


Джон Монтгомери
источник
0

AFAIK это должно быть довольно быстро, но я его не тестировал:

def iterate_prv_nxt(my_list):
    prv, cur, nxt = None, iter(my_list), iter(my_list)
    next(nxt, None)

    while True:
        try:
            if prv:
                yield next(prv), next(cur), next(nxt, None)
            else:
                yield None, next(cur), next(nxt, None)
                prv = iter(my_list)
        except StopIteration:
            break

Пример использования:

>>> my_list = ['a', 'b', 'c']
>>> for prv, cur, nxt in iterate_prv_nxt(my_list):
...    print prv, cur, nxt
... 
None a b
a b c
b c None
Сфисол
источник
0

Я думаю это работает и не сложно

array= [1,5,6,6,3,2]
for i in range(0,len(array)):
    Current = array[i]
    Next = array[i+1]
    Prev = array[i-1]
Сода Фри
источник
Я мало знал, что python поддерживает отрицательные индексы в массивах, спасибо!
ElectRocnic
1
Но в конце он не
сработает
0

Решение в стиле C / C ++:

    foo = 5
    objectsList = [3, 6, 5, 9, 10]
    prev = nex = 0
    
    currentIndex = 0
    indexHigher = len(objectsList)-1 #control the higher limit of list
    
    found = False
    prevFound = False
    nexFound = False
    
    #main logic:
    for currentValue in objectsList: #getting each value of list
        if currentValue == foo:
            found = True
            if currentIndex > 0: #check if target value is in the first position   
                prevFound = True
                prev = objectsList[currentIndex-1]
            if currentIndex < indexHigher: #check if target value is in the last position
                nexFound = True
                nex = objectsList[currentIndex+1]
            break #I am considering that target value only exist 1 time in the list
        currentIndex+=1
    
    if found:
        print("Value %s found" % foo)
        if prevFound:
            print("Previous Value: ", prev)
        else:
            print("Previous Value: Target value is in the first position of list.")
        if nexFound:
            print("Next Value: ", nex)
        else:
            print("Next Value: Target value is in the last position of list.")
    else:
        print("Target value does not exist in the list.")
Марсело Азамбуджа
источник
-1

Питонический и элегантный способ:

objects = [1, 2, 3, 4, 5]
value = 3
if value in objects:
   index = objects.index(value)
   previous_value = objects[index-1]
   next_value = objects[index+1] if index + 1 < len(objects) else None
ImportError
источник
1
Он потерпит неудачу, если valueбудет в конце. Кроме того, возвращает последний элемент, как previous_valueесли бы valueон был первым.
Транг Оул,
Это зависит от ваших требований. Отрицательный индекс из previous_value вернет последний элемент из списка и next_valueвызовет IndexErrorошибку
ImportError
Я довольно часто искал этот метод ... теперь я понял ... спасибо @ImportError. Теперь я могу красиво расширить свой сценарий ..
Азам
Ограничение: valueможет произойти более одного раза objects, но при использовании .index()будет найдено только первое вхождение (или ValueError, если этого не произойдет).
smci