Pythonowa pętla, która również uzyskuje dostęp do poprzednich i następnych wartości

90

Jak mogę iterować listę obiektów, uzyskując dostęp do poprzednich, bieżących i następnych elementów? Jak ten kod C / C ++ w Pythonie?

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
źródło
Co powinno się stać, jeśli foo znajduje się na początku lub na końcu listy? Obecnie wyjdzie to poza granice twojej tablicy.
Brian
2
jeśli potrzebujesz pierwszego wystąpienia „foo”, po dopasowaniu wykonaj „przerwanie” z bloku „for”.
van
Czy chcesz rozpocząć iterację od pierwszego (nie zerowego) elementu i zakończyć iterację od przedostatniego elementu?
smci
Czy gwarantuje się, że foowystąpi dokładnie jeden raz na liście? Jeśli wystąpi, pomnóż, niektóre podejścia tutaj zawiodą lub znajdą tylko pierwsze. A jeśli to się nigdy nie wydarzy, inne podejścia zawiodą lub rzucą wyjątki, takie jak ValueError. Przydałoby się kilka przypadków testowych.
smci
Przykładem tutaj jest sekwencja obiektów, które mają znaną długość i są indeksowane. Niektóre z odpowiedzi są uogólniające na iteratory, które nie zawsze są indeksowalne, nie zawsze mają długości i nie zawsze są skończone ..
smci

Odpowiedzi:

107

To powinno załatwić sprawę.

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]

Oto dokumentacja dotycząca enumeratefunkcji.

Hank Gay
źródło
17
Ale prawdopodobnie najlepszą praktyką jest nie używanie „next” jako nazwy zmiennej, ponieważ jest to funkcja wbudowana.
mkosmala
1
Edytowana wersja tego nadal nie jest logicznie rozsądna: na końcu pętli obji next_będzie tym samym obiektem w ostatniej iteracji, co może mieć niezamierzone efekty uboczne.
TemporalWolf
Stwierdzenie pytania wyraźnie mówi, że OP chce rozpocząć iterację od pierwszego (a nie zerowego) elementu i zakończyć iterację od przedostatniego elementu. Więc indexpowinien uciekać od 1 ... (l-1), a nie 0 ... ltak, jak tutaj, i nie ma potrzeby stosowania specjalnych klauzul if. Przy okazji, jest parametr, enumerate(..., start=1)ale nie dla end. Więc naprawdę nie chcemy używać enumerate().
smci
147

Dotychczasowe rozwiązania zajmowały się tylko listami, a większość z nich kopiuje listę. Z mojego doświadczenia wynika, że ​​wiele razy nie jest to możliwe.

Nie zajmują się też tym, że na liście mogą znajdować się powtarzające się elementy.

Tytuł pytania brzmi „ Poprzednie i następne wartości wewnątrz pętli ”, ale jeśli większość odpowiedzi uruchomisz w pętli, w końcu ponownie przejdziesz przez całą listę dla każdego elementu, aby go znaleźć.

Więc właśnie stworzyłem funkcję, która. używając itertoolsmodułu, dzieli i dzieli iterowalne elementy oraz generuje krotki z poprzednimi i następnymi elementami razem. Nie do końca to, co robi twój kod, ale warto przyjrzeć się temu, ponieważ prawdopodobnie może rozwiązać twój problem.

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)

Następnie użyj go w pętli, a będziesz mieć w nim poprzednie i następne elementy:

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

Wyniki:

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

Będzie działać z każdą listą rozmiarów (ponieważ nie kopiuje listy) oraz z dowolnymi iterowalnymi (pliki, zestawy itp.). W ten sposób możesz po prostu iterować po sekwencji i mieć poprzednie i następne elementy dostępne w pętli. Nie ma potrzeby ponownego wyszukiwania pozycji w sekwencji.

Krótkie wyjaśnienie kodu:

  • tee służy do wydajnego tworzenia 3 niezależnych iteratorów w sekwencji wejściowej
  • chainłączy dwie sekwencje w jedną; jest tutaj używany do dołączania sekwencji jednoelementowej [None]doprevs
  • islicesłuży do tworzenia sekwencji wszystkich elementów z wyjątkiem pierwszego, a następnie chainsłuży do dołączania Nonena jego końcu a
  • Istnieją teraz 3 niezależne sekwencje, some_iterablektóre wyglądają następująco:
    • prevs: None, A, B, C, D, E
    • items: A, B, C, D, E
    • nexts: B, C, D, E, None
  • ostatecznie izipsłuży do zamiany 3 sekwencji w jedną sekwencję trojaczków.

Zauważ, że izipzatrzymuje się, gdy jakakolwiek sekwencja wejściowa zostanie wyczerpana, więc ostatni element prevszostanie zignorowany, co jest poprawne - nie ma takiego elementu, który byłby jego ostatnim elementem prev. Moglibyśmy spróbować oderwać ostatnie elementy od, prevsale izipzachowanie sprawia, że ​​jest to zbędne

Należy również pamiętać, że tee, izip, islicei chainpochodzą z itertoolsmodułu; operują na swoich sekwencjach wejściowych w locie (leniwie), co czyni je wydajnymi i nie wprowadza konieczności posiadania w pamięci całej sekwencji na raz.

W python 3programie pokaże błąd podczas importowania izip, możesz użyć zipzamiast izip. Nie ma potrzeby importu zip, predefiniowane jest w python 3- źródło

nosklo
źródło
3
@becomingGuru: nie jest konieczne przekształcanie SO w lustro dokumentów referencyjnych Pythona. Wszystkie te funkcje są bardzo ładnie wyjaśnione (z przykładami) w oficjalnej dokumentacji
Eli Bendersky
1
@becomingGuru: Dodano łącze do dokumentacji.
nosklo
6
@LakshmanPrasad Jestem w nastroju na wiki, więc dodałem kilka wyjaśnień :-).
Kos
7
Warto wspomnieć, że w Pythonie 3 izipmożna go zastąpić wbudowaną zipfunkcją ;-)
Tomasito665
1
To fajne rozwiązanie, ale wydaje się zbyt skomplikowane. Zobacz stackoverflow.com/a/54995234/1265955, który został zainspirowany tym.
Victoria
6

Korzystając z funkcji list, zwróć 3 krotkę z bieżącymi, poprzednimi i następnymi elementami:

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
źródło
4

Nie wiem, jak to jeszcze nie wyszło, ponieważ wykorzystuje tylko wbudowane funkcje i można je łatwo rozszerzyć na inne przesunięcia:

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)
Eric Czech
źródło
4

Oto wersja wykorzystująca generatory bez błędów granicznych:

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

Zwróć uwagę, że zachowanie brzegowe polega na tym, że nigdy nie szukamy „foo” w pierwszym lub ostatnim elemencie, w przeciwieństwie do kodu. Ponownie, semantyka granic jest dziwna ... i trudno ją pojąć z twojego kodu :)

moshez
źródło
2

używanie wyrażeń warunkowych dla zwięzłości dla Pythona> = 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)
makapuf
źródło
2

Dla każdego, kto szuka rozwiązania tego problemu, a także chce cyklicznie przełączać elementy, poniżej może zadziałać -

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
źródło
podkreślenie w ostatnim next_? Nie można edytować - „musi mieć co najmniej 6 ...”
Xpector
Dlaczego jest to w jakikolwiek sposób lepsze od prostej pętli for i uzyskiwania dostępu objects[i-1], objects[i], objects[i+1]? czy generator? Po prostu wydaje mi się to całkowicie obskurantystyczne. Również niepotrzebnie używa 3x pamięci, ponieważ PREV i NEXT wykonują kopie danych.
smci
@smci Jak uzyskać i+1podejście działające dla ostatniego elementu na liście? To następny element powinien być pierwszym. Wychodzę poza granice.
ElectRocnic
1

Korzystanie z generatorów jest dość proste:

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 )

Zauważ, że testy, które zawierają wartość None i taką samą wartość jak wartość sygnału, nadal działają, ponieważ sprawdzanie wartości sygnału używa „is”, a sygnał jest wartością, której Python nie internuje. Jako sygnał można jednak użyć dowolnej wartości znacznika pojedynczego, co może w pewnych okolicznościach uprościć kod użytkownika.

Wiktoria
źródło
8
„To całkiem proste”
Miguel Stevens
Nie mów „sygnał”, kiedy masz na myśli „wartownik”. Nigdy też nie używaj if iB is signaldo porównywania obiektów pod kątem równości, chyba że signal = None, w takim przypadku po prostu napisz Nonejuż bezpośrednio . Nie używaj iterjako nazwy argumentu, ponieważ przesłania to wbudowane iter(). Jw next. W każdym razie podejście generatora może być po prostuyield prev, curr, next_
smci
@smci Może twój słownik ma inne definicje, które ja używam, dotyczące sygnału i wartownika. Specjalnie użyłem „to”, ponieważ chciałem przetestować konkretny element, a nie inne elementy o równej wartości, „to” jest prawidłowym operatorem dla tego testu. Używanie iter i next shadow tylko do rzeczy, do których nie ma odniesień w inny sposób, więc nie jest to problem, ale uzgodnione, nie jest to najlepsza praktyka. Musisz pokazać więcej kodu, aby podać kontekst ostatniego wniosku.
Victoria
@Victoria: „sentinel [wartość]” jest dobrze zdefiniowanym terminem programowym, „sygnał” nie jest (nie mówiąc o przetwarzaniu sygnału lub sygnałach jądra). Jeśli chodzi o [porównywanie rzeczy w Pythonie z iszamiast ==], to jest to dobrze znana pułapka. Oto kilka powodów, dla których: możesz uciec z tego w przypadku ciągów znaków, ponieważ polegasz na łańcuchach internowania cPythona, ale nawet v1 = 'monkey'; v2 = 'mon'; v3 = 'keywtedy v1 is (v2 + v3)daje False. A jeśli twój kod kiedykolwiek przełączy się na używanie obiektów zamiast int / strings, użycie iszakończy się niepowodzeniem. Więc ogólnie powinieneś używać ==do porównywania równości.
smci
@smci Najtrudniejszym problemem w oprogramowaniu komputerowym jest komunikacja, niedziałająca sieć, ponieważ różne grupy ludzi używają różnych terminów. Jak to się mówi, standardy są świetne, każdy je ma. W pełni rozumiem różnicę między operatorami Python == i is, i właśnie dlatego zdecydowałem się użyć jest. Jeśli spojrzysz poza swoją z góry przyjętą "terminoologię i zasady", zdasz sobie sprawę, że == pozwoliłoby każdemu przedmiotowi, który porównuje równo, zakończyć sekwencję, podczas gdy użycie is zakończy się tylko na określonym obiekcie, który jest używany jako sygnał (lub wartownik, jeśli wolisz).
Victoria
1

Dwa proste rozwiązania:

  1. Jeśli trzeba zdefiniować zmienne dla poprzednich i następnych wartości:
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. Jeśli wszystkie wartości na liście mają zostać przekroczone przez zmienną aktualnej wartości:
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
Serge Tochilov
źródło
0

Możesz po prostu użyć indexlisty, aby znaleźć, gdzie somevaluejest, a następnie w razie potrzeby pobrać poprzednie i następne:


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'


John Montgomery
źródło
0

AFAIK powinno to być dość szybkie, ale nie testowałem tego:

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

Przykładowe użycie:

>>> 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
Sfisol
źródło
0

Myślę, że to działa i nie jest skomplikowane

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]
Soda Frytki
źródło
Nie wiedziałem, że Python obsługuje ujemne indeksy w tablicach, dziękuję!
ElectRocnic
1
Jednak na końcu się to nie powiedzie :(
ElectRocnic
0

Rozwiązanie w stylu bardzo 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.")
Marcelo Azambuja
źródło
-1

Pythonic i elegancki sposób:

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
źródło
1
To się nie powiedzie, jeśli valuejest na końcu. Zwraca także ostatni element tak, previous_valuejakby valuebył pierwszym.
Trang Oul
To zależy od twoich wymagań. Indeks ujemny from previous_value zwróci ostatni element z listy i next_valuepodniesie IndexErrorbłąd
ImportError Kwietnia
Szukałem tej metody dość czasami ... teraz rozumiem ... dzięki @ImportError. Teraz mogę ładnie rozwinąć mój skrypt ...
Azam
Ograniczenie: valuemoże wystąpić więcej niż raz w objects, ale użycie .index()spowoduje odnalezienie pierwszego wystąpienia (lub ValueError, jeśli nie wystąpi).
smci