Цикл 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];
    }
}

Що має статися, якщо foo стоїть на початку або в кінці списку? Наразі це вийде за межі вашого масиву.
Брайан

2
якщо вам потрібно перше входження "foo", тоді зробіть "відрив" від блоку "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
Але, мабуть, найкраща практика не використовувати 'next' як ім'я змінної, оскільки це вбудована функція.
mkosmala

1
Відредагована версія цього ще не логічно звуку: В кінці циклу objі next_буде тим же об'єктом для останньої ітерації, яка може мати ненавмисні побічні ефекти.
TemporalWolf

Заявка на запитання прямо говорить, що OP хоче розпочати ітерацію до 1-го (а не 0-го) елемента і закінчити ітерацію до останнього елемента. Тому indexслід запускати з 1 ... (l-1), а не так, 0 ... lяк у вас тут, і не потребувати спеціальних обставин if-clauses. До речі, є параметр, 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
  • нарешті 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 Я перебуваю у вікі-настрої, тому я додав кілька пояснень :-).
Кос

7
Варто згадати, що в Python 3 izipможна замінити вбудованою zipфункцією ;-)
Tomasito665

1
Це гарне рішення, але воно здається надто складним. Дивіться stackoverflow.com/a/54995234/1265955, який надихнув цей.
Вікторія

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

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)

підкреслення в останньому 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 та те саме значення, що і значення сигналу, все ще працюють, оскільки перевірка значення сигналу використовує "є", а сигнал - це значення, яке Python не інтернує. Будь-яке значення одного маркера може використовуватися як сигнал, що може спростити код користувача за певних обставин.


8
"Це досить просто"
Мігель Стівенс,

Не кажіть "сигнал", маючи на увазі "сторожовий". Крім того, ніколи не використовуйте if iB is signalдля порівняння об’єктів для рівності, якщо signal = None, у цьому випадку просто безпосередньо Noneвже пишіть . Не використовуйте iterяк ім'я аргументу, оскільки це затінює вбудований iter(). Так само next. У будь-якому випадку підхід до генератора може бути простоyield prev, curr, next_
smci

@smci Можливо, ваш словник має різні визначення, що стосуються сигналу та сторожової. Я спеціально використав "є", тому що хотів протестувати для конкретного елемента, а не для інших елементів з однаковим значенням, "є" - правильний оператор для цього тесту. Використання iter та наступної тіні лише на речі, на які не посилаються інакше, тому не проблема, але погоджена, не найкраща практика. Вам потрібно показати більше коду, щоб надати контекст для останньої претензії.
Вікторія

@Victoria: "sentinel [value]" - це чітко визначений програмний термін, "signal" - ні (не кажучи про обробку сигналів або сигнали ядра). Щодо [порівняння речей у Python із isзамість ==], це добре відомий підводний камінь, ось кілька причин, чому: ви можете уникнути цього для рядків, тому що ви покладаєтесь на інтернуючі рядки cPython, але навіть тоді v1 = 'monkey'; v2 = 'mon'; v3 = 'key, тоді v1 is (v2 + v3)дає False. І якщо ваш код коли-небудь переключиться на використання об’єктів замість ints / string, використання isзламається. Отже, загалом ви повинні використовувати ==для порівняння рівності.
smci

@smci Найважчою проблемою в комп'ютерному програмному забезпеченні є зв'язок, мережа не працює, оскільки різні групи людей використовують різні терміни. Як говориться, стандарти - це чудово, у кожного вони є. Я повністю розумію різницю між операторами Python == та 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
Зрештою це не вдасться :(
ElectRocnic

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

1
Це не вдасться, якщо valueце буде в кінці. Також повертає останній елемент, як previous_valueніби valueперший.
Транг Оул,

Це залежить від ваших вимог. Негативний індекс від previous_value поверне останній елемент зі списку і next_valueзросте, IndexErrorі це помилка
ImportError

Я досить часто шукаю цей метод ... тепер я його розумію .. дякую @ImportError. Тепер я можу добре розширити свій сценарій ..
Азам,

Обмеження: valueможе траплятися більше одного разу objects, але використання .index()знайде лише перше (або ValueError, якщо цього не відбудеться).
smci
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.