Python-Schleife, die auch auf vorherige und nächste Werte zugreift


90

Wie kann ich eine Liste von Objekten durchlaufen und auf die vorherigen, aktuellen und nächsten Elemente zugreifen? Wie dieser C / C ++ - Code in 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];
    }
}

Was soll passieren, wenn foo am Anfang oder Ende der Liste steht? Derzeit wird dies außerhalb der Grenzen Ihres Arrays liegen.
Brian

2
Wenn Sie das erste Auftreten von "foo" benötigen, "brechen" Sie den "for" -Block, wenn Sie übereinstimmen.
van

Möchten Sie die Iteration am 1. (nicht am 0.) Element beginnen und die Iteration am vorletzten Element beenden?
smci

Ist garantiert, dass dies foogenau einmal in der Liste vorkommt? Wenn es multipliziert auftritt, schlagen einige Ansätze hier fehl oder finden nur den ersten. Und wenn es nie auftritt, schlagen andere Ansätze fehl oder lösen Ausnahmen wie ValueError aus. Einige Testfälle zu geben hätte geholfen.
smci

Ihr Beispiel hier ist auch eine Folge von Objekten, die beide eine bekannte Länge haben und indizierbar sind. Einige der Antworten hier verallgemeinern sich auf Iteratoren, die nicht immer indizierbar sind, nicht immer Längen haben und nicht immer endlich sind.
smci

Antworten:


106

Dies sollte den Trick tun.

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]

Hier sind die Dokumente zur enumerateFunktion.


17
Aber wahrscheinlich wird empfohlen, 'next' nicht als Variablennamen zu verwenden, da es sich um eine integrierte Funktion handelt.
mkosmala

1
Die bearbeitete Version davon ist immer noch nicht logisch einwandfrei: Am Ende der Schleife objund next_wird das gleiche Objekt für die letzte Iteration sein, was unbeabsichtigte Nebenwirkungen haben kann.
TemporalWolf

Die Frage-Anweisung besagt ausdrücklich, dass OP mit der Iteration am 1. (nicht am 0'-ten) Element beginnen und die Iteration am vorletzten Element beenden möchte. Also indexsollte ablaufen 1 ... (l-1), nicht 0 ... lwie Sie hier haben, und keine Notwendigkeit für die speziellen if-Klauseln. Übrigens gibt es einen Parameter, enumerate(..., start=1)aber nicht für end. Wir wollen also nicht wirklich verwenden enumerate().
smci

147

Bisherige Lösungen befassen sich nur mit Listen, und die meisten kopieren die Liste. Nach meiner Erfahrung ist das oft nicht möglich.

Sie befassen sich auch nicht mit der Tatsache, dass Sie wiederholte Elemente in der Liste haben können.

Der Titel Ihrer Frage lautet " Vorherige und nächste Werte in einer Schleife ". Wenn Sie jedoch die meisten Antworten hier in einer Schleife ausführen, wird die gesamte Liste für jedes Element erneut durchlaufen, um sie zu finden.

Also habe ich gerade eine Funktion erstellt, die. Unter Verwendung des itertoolsModuls wird das iterable geteilt und in Scheiben geschnitten und Tupel mit dem vorherigen und dem nächsten Element zusammen generiert. Nicht genau das, was Ihr Code tut, aber es lohnt sich, einen Blick darauf zu werfen, da er wahrscheinlich Ihr Problem lösen kann.

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)

Verwenden Sie es dann in einer Schleife, und Sie haben vorherige und nächste Elemente darin:

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

Die Ergebnisse:

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

Es funktioniert mit jeder Größenliste (da die Liste nicht kopiert wird) und mit jeder iterierbaren Liste (Dateien, Mengen usw.). Auf diese Weise können Sie einfach die Sequenz durchlaufen und die vorherigen und nächsten Elemente in der Schleife verfügbar machen. Sie müssen nicht erneut nach dem Element in der Sequenz suchen.

Eine kurze Erklärung des Codes:

  • tee wird verwendet, um effizient 3 unabhängige Iteratoren über die Eingabesequenz zu erstellen
  • chainverbindet zwei Sequenzen zu einer; es ist hier verwendet , um ein Einzelelement - Sequenz anhängen [None]zuprevs
  • islicewird verwendet, um eine Folge aller Elemente außer dem ersten chainzu erstellen , und wird dann verwendet, um ein Nonean sein Ende anzuhängen
  • Es gibt jetzt 3 unabhängige Sequenzen some_iterable, die wie folgt aussehen:
    • prevs:: None, A, B, C, D, E
    • items:: A, B, C, D, E
    • nexts:: B, C, D, E, None
  • wird schließlich izipverwendet, um 3 Sequenzen in eine Sequenz von Tripletts zu ändern.

Beachten Sie, dass dies izipstoppt, wenn eine Eingabesequenz erschöpft ist, sodass das letzte Element von prevsignoriert wird, was korrekt ist - es gibt kein solches Element, das das letzte Element sein würde prev. Wir könnten versuchen, die letzten Elemente zu entfernen, prevsaber izipdas Verhalten macht dies überflüssig

Beachten Sie auch , dass tee, izip, isliceund chainkommen aus dem itertoolsModul; Sie arbeiten ihre Eingabesequenzen im laufenden Betrieb (träge), was sie effizient macht und nicht die Notwendigkeit mit sich bringt, die gesamte Sequenz zu jedem Zeitpunkt auf einmal im Speicher zu haben.

In python 3wird beim Importieren ein Fehler angezeigt izip, den Sie zipanstelle von verwenden können izip. Keine Notwendigkeit zu importieren zip, ist es vordefiniert in python 3- Quelle


3
@becomingGuru: Es ist nicht erforderlich, SO in einen Spiegel der Python-Referenzdokumente zu verwandeln. Alle diese Funktionen werden sehr schön (mit Beispielen) in der offiziellen Dokumentation erklärt
Eli Bendersky

1
@becomingGuru: Link zur Dokumentation hinzugefügt.
Nosklo

6
@ LakshmanPrasad Ich bin in einer Wiki-Stimmung, also habe ich einige Erklärungen hinzugefügt :-).
Kos

7
Es könnte erwähnenswert sein, dass in Python 3 izipdurch die eingebaute zipFunktion ersetzt werden kann ;-)
Tomasito665

1
Dies ist eine schöne Lösung, aber sie scheint zu komplex. Siehe stackoverflow.com/a/54995234/1265955, das von diesem inspiriert wurde.
Victoria

6

Geben Sie unter Verwendung eines Listenverständnisses ein 3-Tupel mit aktuellen, vorherigen und nächsten Elementen zurück:

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

Ich weiß nicht, wie dies noch nicht geschehen ist, da es nur integrierte Funktionen verwendet und leicht auf andere Offsets erweiterbar ist:

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

Hier ist eine Version mit Generatoren ohne Grenzfehler:

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

Bitte beachten Sie, dass das Grenzverhalten darin besteht, dass wir im Gegensatz zu Ihrem Code niemals im ersten oder letzten Element nach "foo" suchen. Auch hier ist die Grenzsemantik seltsam ... und aus Ihrem Code schwer zu ergründen :)


2

Verwenden von bedingten Ausdrücken für die Prägnanz für 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

Für alle, die nach einer Lösung suchen, die auch die Elemente durchlaufen möchte, könnte das Folgende funktionieren -

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)

im letzten next_ unterstreichen? Kann nicht bearbeiten - "muss mindestens 6 sein ..."
Xpector

Warum ist dies in irgendeiner Weise einer einfachen for-Schleife und einem einfachen Zugriff vorzuziehen objects[i-1], objects[i], objects[i+1]? oder ein Generator? Es scheint mir einfach total obskurant zu sein. Außerdem wird unnötig 3x Speicher verwendet, da PREV und NEXT Kopien der Daten erstellen.
smci

@smci Wie funktioniert der i+1Ansatz für das letzte Element in der Liste? Das nächste Element sollte dann das erste sein. Ich komme aus dem Ruder.
ElectRocnic

1

Mit Generatoren ist es ganz einfach:

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 )

Beachten Sie, dass Tests, die None und denselben Wert wie der Signalwert enthalten, weiterhin funktionieren, da bei der Überprüfung des Signalwerts "is" verwendet wird und das Signal ein Wert ist, den Python nicht interniert. Jeder Singleton-Markierungswert kann jedoch als Signal verwendet werden, was unter bestimmten Umständen den Benutzercode vereinfachen kann.


8
"Es ist ganz einfach"
Miguel Stevens

Sagen Sie nicht "Signal", wenn Sie "Sentinel" meinen. Verwenden Sie niemals if iB is signal, um Objekte auf Gleichheit zu vergleichen, es sei denn, signal = None. In diesem Fall schreiben Sie einfach bereits direkt None. Nicht iterals Argumentname verwenden, da dies das integrierte Element beschattet iter(). Das Gleiche gilt next. Auf yield prev, curr, next_
jeden Fall

@smci Vielleicht hat dein Wörterbuch andere Definitionen als Signal- und Sentinel-Definitionen. Ich habe speziell "is" verwendet, weil ich für das bestimmte Element testen wollte, nicht für andere Elemente mit gleichem Wert. "Is" ist der richtige Operator für diesen Test. Verwendung von iter und next shadow nur Dinge, auf die nicht anders verwiesen wird, also kein Problem, aber vereinbart, nicht die beste Vorgehensweise. Sie müssen mehr Code anzeigen, um den Kontext für Ihren letzten Anspruch bereitzustellen.
Victoria

@ Victoria: 'Sentinel [Wert]' ist ein genau definierter Softwarebegriff, 'Signal' nicht (nicht über Signalverarbeitung oder Kernelsignale). Was [das Vergleichen von Dingen in Python mit isstatt ==] betrifft, so ist dies eine bekannte Gefahr. Hier sind mehrere Gründe dafür: Sie können damit für Zeichenfolgen durchkommen, weil Sie sich darauf verlassen, dass cPython Zeichenfolgen interniert, aber selbst v1 = 'monkey'; v2 = 'mon'; v3 = 'keydann v1 is (v2 + v3)gibt es False. Und wenn Ihr Code jemals auf die Verwendung von Objekten anstelle von Ints / Strings umschaltet, wird die Verwendung unterbrochen is. Im Allgemeinen sollten Sie also ==die Gleichheit vergleichen.
smci

@smci Das schwierigste Problem bei Computersoftware ist die Kommunikation. Das Netzwerk funktioniert nicht, da unterschiedliche Personengruppen unterschiedliche Begriffe verwenden. Wie das Sprichwort sagt, sind Standards großartig, jeder hat einen. Ich verstehe den Unterschied zwischen den Operatoren Python == und is voll und ganz, und genau deshalb habe ich mich für die Verwendung von entschieden. Wenn Sie über Ihre vorgefassten "Terminoologie und Regeln" hinausblicken, werden Sie feststellen, dass == jedes vergleichbare Element zum Beenden der Sequenz zulässt, während die Verwendung von is nur bei dem bestimmten Objekt endet, das als Signal verwendet wird (oder Sentinel, wenn Sie es vorziehen).
Victoria

1

Zwei einfache Lösungen:

  1. Wenn Variablen für vorherige und nächste Werte definiert werden müssen:
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. Wenn alle Werte in der Liste von der aktuellen Wertvariablen durchlaufen werden müssen:
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

Sie können einfach indexauf der Liste verwenden, um herauszufinden, wo somevaluesich die Daten befinden, und dann die vorherigen und nächsten nach Bedarf abrufen:


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 das sollte ziemlich schnell gehen, aber ich habe es nicht getestet:

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

Anwendungsbeispiel:

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

Ich denke das funktioniert und ist nicht kompliziert

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]

Ich wusste nicht, dass Python negative Indizes in Arrays unterstützt, danke!
ElectRocnic

1
Es wird jedoch am Ende scheitern :(
ElectRocnic

0

Sehr C / C ++ - Lösung:

    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

Pythonische und elegante Art:

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
Es wird fehlschlagen, wenn valuees am Ende ist. Gibt auch das letzte Element zurück, als previous_valuewäre valuees das erste.
Trang Oul

Das hängt von Ihren Anforderungen ab. Negativer Index von previous_value gibt das letzte Element aus der Liste zurück und löst aus next_value, IndexErrorund das ist ein Fehler
ImportError

Ich habe diese Methode manchmal gesucht. Jetzt verstehe ich sie. Danke @ImportError. Jetzt kann ich mein Skript schön erweitern ..
Azam

Einschränkung: Kann valuemehrmals vorkommen objects, aber bei Verwendung .index()wird nur das erste Vorkommen gefunden (oder ValueError, wenn es nicht auftritt).
smci
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.