Boucle Python qui accède également aux valeurs précédentes et suivantes


90

Comment puis-je parcourir une liste d'objets, accéder aux éléments précédents, actuels et suivants? Vous aimez ce code C / C ++, en 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];
    }
}

Que se passe-t-il si foo est au début ou à la fin de la liste? Actuellement, cela sortira des limites de votre tableau.
Brian

2
si vous avez besoin de la première occurrence de "foo", alors faites "break" du bloc "for" lorsqu'il correspond.
van

Voulez-vous commencer l'itération au premier élément (pas au 0e) et terminer l'itération au dernier élément avant un?
smci

Est-il garanti que cela foose produit exactement une fois dans la liste? Si cela se produit, plusieurs approches échoueront ici ou ne trouveront que la première. Et si cela ne se produit jamais, d'autres approches échoueront ou lèveront des exceptions comme ValueError. Donner quelques tests aurait aidé.
smci

En outre, votre exemple ici est une séquence d'objets, qui ont tous deux une longueur connue et sont indexables. Certaines des réponses ici se généralisent aux itérateurs, qui ne sont pas toujours indexables, n'ont pas toujours des longueurs et ne sont pas toujours finies ..
smci

Réponses:


107

Cela devrait faire l'affaire.

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]

Voici la documentation sur la enumeratefonction.


17
Mais il est probablement préférable de ne pas utiliser «suivant» comme nom de variable, car il s'agit d'une fonction intégrée.
mkosmala

1
La version modifiée de ce n'est toujours pas logiquement saine: à la fin de la boucle objet next_sera le même objet pour la dernière itération, qui peut avoir des effets secondaires involontaires.
TemporalWolf

L'énoncé de question indique explicitement que OP veut commencer l'itération au 1 ème élément (et non au 0 ème), et terminer l'itération au dernier élément. Donc indexdevrait fonctionner à partir de 1 ... (l-1), pas 0 ... lcomme vous l'avez ici, et pas besoin de clauses if à casse spéciale. Btw, il y a un paramètre enumerate(..., start=1)mais pas pour end. Nous ne voulons donc pas vraiment utiliser enumerate().
smci

147

Les solutions jusqu'à présent ne concernent que les listes, et la plupart copient la liste. D'après mon expérience, ce n'est souvent pas possible.

En outre, ils ne traitent pas du fait que vous pouvez avoir des éléments répétés dans la liste.

Le titre de votre question dit " Valeurs précédentes et suivantes dans une boucle ", mais si vous exécutez la plupart des réponses ici dans une boucle, vous finirez par parcourir à nouveau la liste entière sur chaque élément pour le trouver.

Donc je viens de créer une fonction qui. en utilisant le itertoolsmodule, divise et tranche l'itérable, et génère des tuples avec les éléments précédent et suivant ensemble. Ce n'est pas exactement ce que fait votre code, mais cela vaut la peine d'y jeter un coup d'œil, car il peut probablement résoudre votre problème.

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)

Ensuite, utilisez-le dans une boucle, et vous aurez les éléments précédents et suivants:

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

Les resultats:

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

Cela fonctionnera avec n'importe quelle liste de taille (car il ne copie pas la liste), et avec n'importe quel itérable (fichiers, ensembles, etc.). De cette façon, vous pouvez simplement parcourir la séquence et avoir les éléments précédents et suivants disponibles dans la boucle. Pas besoin de rechercher à nouveau l'élément dans la séquence.

Une brève explication du code:

  • tee est utilisé pour créer efficacement 3 itérateurs indépendants sur la séquence d'entrée
  • chainrelie deux séquences en une seule; il est utilisé ici pour ajouter une séquence à un seul élément[None] à àprevs
  • islice est utilisé pour faire une séquence de tous les éléments sauf le premier, puis chain est utilisé pour ajouter unNone à sa fin
  • Il y a maintenant 3 séquences indépendantes basées sur some_iterable qui ressemblent à:
    • prevs: None, A, B, C, D, E
    • items: A, B, C, D, E
    • nexts: B, C, D, E, None
  • enfin izip est utilisé pour changer 3 séquences en une séquence de triplets.

Notez que izips'arrête quand une séquence d'entrée est épuisée, donc le dernier élément de prevssera ignoré, ce qui est correct - il n'y a pas d'élément tel que le dernier élément serait le sien prev. Nous pourrions essayer de supprimer les derniers éléments de prevsmaisizip le comportement de ce dernier rend cela redondant

Notez également que tee, izip, isliceetchain venir du itertoolsmodule; ils opèrent sur leurs séquences d'entrée à la volée (paresseusement), ce qui les rend efficaces et n'introduit pas le besoin d'avoir toute la séquence en mémoire à la fois à tout moment.

Dans python 3, il affichera une erreur lors de l'importation izip, vous pouvez utiliser à la zipplace de izip. Pas besoin d'importer zip, il est prédéfini dans python 3- source


3
@becomingGuru: il n'est pas nécessaire de transformer SO en un miroir de la documentation de référence Python. Toutes ces fonctions sont très bien expliquées (avec des exemples) dans la documentation officielle
Eli Bendersky

1
@becomingGuru: Ajout d'un lien vers la documentation.
nosklo

6
@LakshmanPrasad Je suis d'humeur wiki donc j'ai ajouté quelques explications :-).
Kos le

7
Il pourrait être intéressant de mentionner que dans Python 3 izippeut être remplacé par la zipfonction intégrée ;-)
Tomasito665

1
C'est une bonne solution, mais elle semble trop complexe. Voir stackoverflow.com/a/54995234/1265955 qui a été inspiré par celui-ci.
Victoria

6

En utilisant une compréhension de liste, retournez un 3-tuple avec les éléments actuels, précédents et suivants:

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

Je ne sais pas comment cela n'est pas encore arrivé car il n'utilise que des fonctions intégrées et est facilement extensible à d'autres décalages:

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

Voici une version utilisant des générateurs sans erreur de limite:

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

Veuillez noter que le comportement aux limites est que nous ne cherchons jamais "foo" dans le premier ou le dernier élément, contrairement à votre code. Encore une fois, la sémantique des limites est étrange ... et est difficile à comprendre à partir de votre code :)


2

utilisation d'expressions conditionnelles pour la concision pour 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

Pour tous ceux qui recherchent une solution à ce problème et qui souhaitent également faire défiler les éléments, ci-dessous pourrait fonctionner -

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)

souligné dans le dernier suivant_? Impossible de modifier - "doit être au moins 6 ..."
Xpector

Pourquoi est-ce préférable à une simple boucle for et à un accès objects[i-1], objects[i], objects[i+1]? ou un générateur? Cela me semble totalement obscurantiste. En outre, il utilise inutilement la mémoire 3x puisque PREV et NEXT font des copies des données.
smci

@smci Comment faites-vous fonctionner l' i+1approche pour le dernier élément de la liste? C'est l'élément suivant devrait alors être le premier. Je sors des limites.
ElectRocnic

1

En utilisant des générateurs, c'est assez simple:

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 )

Notez que les tests qui incluent None et la même valeur que la valeur du signal fonctionnent toujours, car la vérification de la valeur du signal utilise «est» et le signal est une valeur que Python ne fait pas en interne. Toute valeur de marqueur de singleton peut cependant être utilisée comme signal, ce qui peut simplifier le code utilisateur dans certaines circonstances.


8
"C'est assez simple"
Miguel Stevens

Ne dites pas «signal» quand vous voulez dire «sentinelle». Aussi, ne jamais utiliser if iB is signalpour comparer des objets pour l'égalité, sauf si signal = None, auquel cas écrivez Nonedéjà directement . Ne l'utilisez pas itercomme nom d'argument car cela masque la fonction intégrée iter(). Idem next. Quoi qu'il en soit, l'approche du générateur peut simplement êtreyield prev, curr, next_
smci

@smci Peut-être que votre dictionnaire a des définitions différentes des miennes, concernant le signal et la sentinelle. J'ai spécifiquement utilisé "est" parce que je voulais tester pour l'élément spécifique, pas pour d'autres éléments de valeur égale, "est" est l'opérateur correct pour ce test. Utilisation de iter et next shadow uniquement des choses qui ne sont pas référencées autrement, donc pas un problème, mais accepté, pas la meilleure pratique. Vous devez afficher plus de code pour fournir le contexte de votre dernière réclamation.
Victoria le

@Victoria: 'sentinel [valeur]' est un terme logiciel bien défini, 'signal' ne l'est pas (ne parle pas de traitement du signal, ni de signaux du noyau). Quant à [comparer des choses en Python avec isau lieu de ==], c'est un piège bien connu, voici plusieurs raisons pour lesquelles: vous pouvez vous en tirer pour les chaînes, car vous comptez sur les chaînes internes cPython, mais même alors v1 = 'monkey'; v2 = 'mon'; v3 = 'key, cela v1 is (v2 + v3)donne False. Et si votre code passe jamais à l'utilisation d'objets au lieu d'entiers / chaînes, l'utilisation issera interrompue. Donc, en général, vous devriez utiliser ==pour comparer l'égalité.
smci le

@smci Le problème le plus difficile dans les logiciels informatiques est la communication, le réseautage ne fonctionne pas, en raison de différents groupes de personnes utilisant des termes différents. Comme on dit, les normes sont excellentes, tout le monde en a une. Je comprends parfaitement la différence entre les opérateurs Python == et is, et c'est exactement pourquoi j'ai choisi d'utiliser is. Si vous regardez au-delà de votre "terminoologie et règles" préconçues, vous vous rendrez compte que == permettrait à tout élément qui se compare égal à terminer la séquence, alors que l'utilisation de is se terminera uniquement à l'objet spécifique qui est utilisé comme signal (ou sentinelle si vous préférez).
Victoria le

1

Deux solutions simples:

  1. Si des variables pour les valeurs précédentes et suivantes doivent être définies:
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. Si toutes les valeurs de la liste doivent être parcourues par la variable de valeur actuelle:
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

Vous pouvez simplement utiliser indexsur la liste pour trouver où se somevaluetrouve, puis obtenir le précédent et le suivant au besoin:


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 cela devrait être assez rapide, mais je ne l'ai pas testé:

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

Exemple d'utilisation:

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

Je pense que cela fonctionne et pas compliqué

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]

Je ne savais pas que python supportait les indices négatifs dans les tableaux, merci!
ElectRocnic

1
Cela échouera à la fin :(
ElectRocnic

0

Solution de style très 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

Façon pythonique et élégante:

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
Il échouera si valuec'est à la fin. En outre, renvoie le dernier élément comme previous_valuesi valueétait le premier.
Trang Oul

Cela dépend de vos besoins. Indice négatif de previous_value renverra le dernier élément de la liste et next_valuesoulèvera IndexErroret c'est une erreur
ImportError

J'ai été à la recherche de cette méthode depuis un certain temps ... maintenant je l'obtiens..merci @ImportError. Maintenant, je peux bien développer mon script ..
Azam

Limitation: valuepeut se produire plus d'une fois objects, mais l'utilisation .index()ne trouvera que sa première occurrence (ou ValueError si elle ne se produit pas).
smci
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.