Ciclo Python che accede anche ai valori precedenti e successivi


91

Come posso iterare su un elenco di oggetti, accedendo agli elementi precedenti, correnti e successivi? Ti piace questo codice C / C ++, in Python?


Cosa dovrebbe accadere se foo è all'inizio o alla fine dell'elenco? Attualmente, questo uscirà dai limiti del tuo array.
Brian

2
se hai bisogno della prima occorrenza di "foo", allora esegui "break" dal blocco "for" quando viene abbinato.
furgone

Vuoi iniziare l'iterazione dall'elemento 1'st (non 0'th) e terminare l'iterazione dal penultimo elemento?
smci

È garantito che si fooverifichi esattamente una volta nell'elenco? Se si verifica moltiplica, alcuni approcci qui falliranno o troveranno solo il primo. E se non si verifica mai, altri approcci falliranno o genereranno eccezioni come ValueError. Dare alcuni casi di prova avrebbe aiutato.
smci

Inoltre, il tuo esempio qui è una sequenza di oggetti, che hanno entrambi una lunghezza nota ed è indicizzabile. Alcune delle risposte qui stanno generalizzando agli iteratori, che non sono sempre indicizzabili, non hanno sempre lunghezze e non sono sempre finite ..
smci

Risposte:


111

Questo dovrebbe fare il trucco.

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]

Ecco i documenti sulla enumeratefunzione.


18
Ma probabilmente è consigliabile non utilizzare "next" come nome della variabile, poiché è una funzione incorporata.
mkosmala

1
La versione modificata di questo non è ancora logicamente valida: alla fine del ciclo obje next_sarà lo stesso oggetto per l'ultima iterazione, che potrebbe avere effetti collaterali indesiderati.
TemporalWolf

L'affermazione della domanda dice esplicitamente che OP vuole iniziare a iterare dall'elemento 1'st (non 0'th) e terminare l'iterazione dal penultimo elemento. Quindi indexdovrebbe partire da 1 ... (l-1), non 0 ... lcome hai qui, e non c'è bisogno delle clausole if in caso speciale. A proposito, c'è un parametro enumerate(..., start=1)ma non per end. Quindi non vogliamo davvero usare enumerate().
smci

147

Le soluzioni fino ad ora riguardano solo gli elenchi e la maggior parte sta copiando l'elenco. Nella mia esperienza molte volte non è possibile.

Inoltre, non si occupano del fatto che puoi avere elementi ripetuti nell'elenco.

Il titolo della tua domanda dice " Valori precedenti e successivi all'interno di un ciclo ", ma se esegui la maggior parte delle risposte qui all'interno di un ciclo, finirai per iterare di nuovo l'intero elenco su ogni elemento per trovarlo.

Quindi ho appena creato una funzione che. utilizzando il itertoolsmodulo, divide e affetta l'iterabile e genera tuple con gli elementi precedente e successivo insieme. Non esattamente quello che fa il tuo codice, ma vale la pena dare un'occhiata, perché probabilmente può risolvere il tuo problema.

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)

Quindi usalo in un ciclo e avrai gli elementi precedenti e successivi in ​​esso:

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

I risultati:

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

Funzionerà con qualsiasi elenco di dimensioni (perché non copia l'elenco) e con qualsiasi iterabile (file, set, ecc.). In questo modo puoi semplicemente iterare sulla sequenza e avere gli elementi precedenti e successivi disponibili all'interno del ciclo. Non è necessario cercare di nuovo l'elemento nella sequenza.

Una breve spiegazione del codice:

  • tee viene utilizzato per creare in modo efficiente 3 iteratori indipendenti sulla sequenza di input
  • chaincollega due sequenze in una; è usato qui per aggiungere una sequenza [None]di un singolo elemento aprevs
  • isliceviene utilizzato per creare una sequenza di tutti gli elementi tranne il primo, quindi chainviene utilizzato per aggiungere un Nonealla sua fine
  • Ora ci sono 3 sequenze indipendenti basate su some_iterablequesto aspetto come:
    • prevs: None, A, B, C, D, E
    • items: A, B, C, D, E
    • nexts: B, C, D, E, None
  • infine izipè usato per cambiare 3 sequenze in una sequenza di terzine.

Nota che si izipinterrompe quando una sequenza di input viene esaurita, quindi l'ultimo elemento di prevsverrà ignorato, il che è corretto: non esiste un elemento tale che l'ultimo elemento sarebbe il suo prev. Potremmo provare a togliere gli ultimi elementi da prevsmaizip il comportamento di rende ridondante

Si noti inoltre che tee, izip, islicee chainprovengono dal itertoolsmodulo; agiscono sulle loro sequenze di input al volo (pigramente), il che li rende efficienti e non introduce la necessità di avere l'intera sequenza in memoria contemporaneamente e in qualsiasi momento.

In python 3, mostrerà un errore durante l'importazione izip, puoi usare al zipposto di izip. Non è necessario importare zip, è predefinito in python 3- source


3
@becomingGuru: non è necessario trasformare SO in un mirror dei documenti di riferimento di Python. Tutte queste funzioni sono spiegate molto bene (con esempi) nella documentazione ufficiale
Eli Bendersky,

1
@becomingGuru: aggiunto un collegamento alla documentazione.
nosklo

6
@ LakshmanPrasad Sono di umore wiki, quindi ho aggiunto alcune spiegazioni :-).
Kos

7
Potrebbe valere la pena ricordare che in Python 3 izippuò essere sostituito con la zipfunzione incorporata ;-)
Tomasito665

1
Questa è una bella soluzione, ma sembra eccessivamente complessa. Vedi stackoverflow.com/a/54995234/1265955 che è stato ispirato da questo.
Victoria

6

Usando una lista di comprensione, restituisci una 3-tupla con gli elementi corrente, precedente e successivo:

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

Non so come non sia ancora venuto fuori poiché utilizza solo funzioni integrate ed è facilmente estendibile ad altri offset:

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

Ecco una versione che utilizza generatori senza errori di confine:

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

Si noti che il comportamento limite è che non cerchiamo mai "foo" nel primo o nell'ultimo elemento, a differenza del codice. Di nuovo, la semantica del confine è strana ... e difficile da capire dal tuo codice :)


2

usando espressioni condizionali per concisione per 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

Per chiunque cerchi una soluzione a questo problema con anche il desiderio di ciclare gli elementi, di seguito potrebbe funzionare:

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)

carattere di sottolineatura nell'ultimo next_? Impossibile modificare - "deve essere almeno 6 ..."
Xpector

Perché questo è in qualche modo preferibile a un semplice ciclo for e accesso objects[i-1], objects[i], objects[i+1]? o un generatore? A me sembra totalmente oscurantista. Inoltre utilizza inutilmente 3x di memoria poiché PREV e NEXT fanno copie dei dati.
smci

@smci Come si ottiene il file i+1 approccio per l'ultimo elemento nell'elenco? L'elemento successivo dovrebbe quindi essere il primo. Esco dal campo.
ElectRocnic

1

Utilizzando i generatori, è abbastanza semplice:

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 )

Si noti che i test che includono None e lo stesso valore del valore del segnale funzionano ancora, perché il controllo del valore del segnale utilizza "is" e il segnale è un valore che Python non integra. Qualsiasi valore di marcatore singleton può essere utilizzato come segnale, tuttavia, il che potrebbe semplificare il codice utente in alcune circostanze.


9
"È abbastanza semplice"
Miguel Stevens

Non dire "segnale" quando intendi "sentinella". Inoltre, non usare mai if iB is signalper confrontare oggetti per l'uguaglianza, a meno che signal = None, nel qual caso scrivi Nonegià direttamente . Non usare itercome nome dell'argomento poiché ombreggia il builtin iter(). Idem next. Comunque l'approccio del generatore può essere semplicementeyield prev, curr, next_
smci

@smci Forse il tuo dizionario ha definizioni diverse dalla mia, per quanto riguarda segnale e sentinella. Ho usato specificamente "è" perché volevo testare per l'elemento specifico, non per altri elementi con lo stesso valore, "è" è l'operatore corretto per quel test. Uso di iter e next shadow solo per cose che non sono altrimenti referenziate, quindi non è un problema, ma concordato, non la migliore pratica. Devi mostrare più codice per fornire contesto per la tua ultima richiesta.
Victoria

@Victoria: "sentinel [value]" è un termine software ben definito, "signal" non lo è (non si parla di elaborazione del segnale o segnali del kernel). Per quanto riguarda [confrontare le cose in Python con isinvece di ==], è una trappola ben nota, qui ci sono molti motivi per cui: puoi farla franca per le stringhe, perché ti affidi a stringhe interning cPython, ma anche allora v1 = 'monkey'; v2 = 'mon'; v3 = 'key, poi v1 is (v2 + v3)False. E se il tuo codice passa all'uso di oggetti invece di int / stringhe, l'utilizzo issi interromperà. Quindi, in generale, dovresti usare ==per confrontare l'uguaglianza.
smci

@smci Il problema più difficile nel software del computer sono le comunicazioni, la rete non funziona, a causa di diversi gruppi di persone che utilizzano termini diversi. Come si suol dire, gli standard sono ottimi, tutti ne hanno uno. Comprendo appieno la differenza tra gli operatori Python == e is, ed è proprio per questo che ho scelto di usare is. Se guardassi oltre la tua "terminologia e regole" preconcette, ti renderai conto che == consentirebbe a qualsiasi elemento che confronta uguale a terminare la sequenza, mentre l'uso di è terminerà solo sull'oggetto specifico che viene utilizzato come segnale (o sentinella se preferisci).
Victoria

1

Due semplici soluzioni:

  1. Se è necessario definire variabili sia per i valori precedenti che per quelli successivi:
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. Se tutti i valori nell'elenco devono essere attraversati dalla variabile del valore corrente:
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

Puoi semplicemente usare indexl'elenco per trovare dove si somevaluetrova e quindi ottenere il precedente e il successivo secondo necessità:


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

Per quanto ne so, dovrebbe essere abbastanza veloce, ma non l'ho testato:

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

Utilizzo di esempio:

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

Penso che funzioni e non sia complicato

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]

Non sapevo che python supporta gli indici negativi negli array, grazie!
ElectRocnic

1
Alla fine però
fallirà

0

Soluzione in stile C / C ++ molto:

    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

Modo pitonico ed elegante:

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
Fallirà se valueè alla fine. Inoltre, restituisce l'ultimo elemento come previous_valuese valuefosse il primo.
Trang Oul,

Dipende dalle tue esigenze. L'indice negativo di previous_value restituirà l'ultimo elemento dell'elenco e next_valueverrà IndexErrorgenerato e questo è un errore
ImportError

Ho cercato questo metodo abbastanza a volte..ora lo capisco..grazie @ImportError. Ora posso espandere bene il mio script ..
Azam

Limitazione: valuepotrebbe verificarsi più di una volta in objects, ma l'utilizzo .index()troverà solo la sua prima occorrenza (o ValueError se non si verifica).
smci
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.