Bucle de Python que también accede a los valores anteriores y siguientes


90

¿Cómo puedo iterar sobre una lista de objetos, accediendo a los elementos anterior, actual y siguiente? ¿Como este código 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];
    }
}

¿Qué debería suceder si foo está al principio o al final de la lista? Actualmente, esto saldrá de los límites de su matriz.
Brian

2
si necesita la primera aparición de "foo", entonces haga "romper" del bloque "for" cuando coincida.
furgoneta

¿Desea comenzar a iterar en el primer elemento (no en el 0) y terminar de iterar en el último elemento menos uno?
smci

¿Está garantizado que fooocurra exactamente una vez en la lista? Si ocurre multiplicar, algunos enfoques aquí fallarán, o solo encontrarán el primero. Y si nunca ocurre, otros enfoques fallarán o lanzarán excepciones como ValueError. Dar algunos casos de prueba habría ayudado.
smci

Además, su ejemplo aquí es una secuencia de objetos, que tienen una longitud conocida y son indexables. Algunas de las respuestas aquí se están generalizando a iteradores, que no siempre son indexables, no siempre tienen longitudes y no siempre son finitas ..
smci

Respuestas:


107

Esto debería funcionar.

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]

Aquí están los documentos sobre la enumeratefunción.


17
Pero probablemente sea una buena práctica no usar 'siguiente' como nombre de variable, ya que es una función incorporada.
mkosmala

1
La versión editada de esto todavía no es lógicamente sólida: al final del ciclo objy next_será el mismo objeto para la última iteración, lo que puede tener efectos secundarios no deseados.
TemporalWolf

El enunciado de la pregunta dice explícitamente que OP quiere comenzar a iterar en el primer elemento (no en el 0) y terminar de iterar en el último elemento menos uno. Por indexlo tanto, debería ejecutarse desde 1 ... (l-1), no 0 ... lcomo lo ha hecho aquí, y no hay necesidad de las cláusulas if en mayúsculas y minúsculas. Por cierto, hay un parámetro enumerate(..., start=1)pero no para end. Entonces realmente no queremos usar enumerate().
smci

147

Las soluciones hasta ahora solo tratan con listas, y la mayoría está copiando la lista. En mi experiencia, muchas veces eso no es posible.

Además, no abordan el hecho de que puede tener elementos repetidos en la lista.

El título de su pregunta dice " Valores anteriores y siguientes dentro de un ciclo ", pero si ejecuta la mayoría de las respuestas aquí dentro de un ciclo, terminará iterando sobre la lista completa nuevamente en cada elemento para encontrarlo.

Así que acabo de crear una función que. usando el itertoolsmódulo, divide y corta el iterable, y genera tuplas con los elementos anterior y siguiente juntos. No es exactamente lo que hace su código, pero vale la pena echarle un vistazo, porque probablemente pueda resolver su 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)

Luego utilícelo en un bucle, y tendrá elementos anteriores y siguientes:

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

Los resultados:

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

Funcionará con listas de cualquier tamaño (porque no copia la lista) y con cualquier iterable (archivos, conjuntos, etc.). De esta manera, puede iterar sobre la secuencia y tener los elementos anterior y siguiente disponibles dentro del ciclo. No es necesario volver a buscar el elemento en la secuencia.

Una breve explicación del código:

  • tee se utiliza para crear de manera eficiente 3 iteradores independientes sobre la secuencia de entrada
  • chainenlaza dos secuencias en una; se usa aquí para agregar una secuencia de un solo elemento [None]aprevs
  • islicese usa para hacer una secuencia de todos los elementos excepto el primero, luego chainse usa para agregar un Noneal final
  • Ahora hay 3 secuencias independientes basadas en some_iterableese aspecto:
    • prevs: None, A, B, C, D, E
    • items: A, B, C, D, E
    • nexts: B, C, D, E, None
  • finalmente izipse usa para cambiar 3 secuencias en una secuencia de tripletes.

Tenga en cuenta que se izipdetiene cuando se agota cualquier secuencia de entrada, por lo que el último elemento de prevsse ignorará, lo cual es correcto: no existe tal elemento que el último elemento sería suyo prev. Podríamos intentar quitar los últimos elementos de prevspero izipel comportamiento hace que eso sea redundante

También tenga en cuenta que tee, izip, islicey chainvenir del itertoolsmódulo; operan en sus secuencias de entrada sobre la marcha (perezosamente), lo que las hace eficientes y no introduce la necesidad de tener la secuencia completa en la memoria de una vez en ningún momento.

En python 3, mostrará un error al importar izip, puede usar en ziplugar de izip. No hay necesidad de importación zip, que está predefinido en python 3- fuente


3
@becomingGuru: no es necesario convertir SO en un espejo de los documentos de referencia de Python. Todas estas funciones están muy bien explicadas (con ejemplos) en la documentación oficial
Eli Bendersky

1
@becomingGuru: se agregó un enlace a la documentación.
nosklo

6
@LakshmanPrasad Estoy de humor wiki, así que agregué algunas explicaciones :-).
Kos

7
Vale la pena mencionar que en Python 3 izipse puede reemplazar con la zipfunción incorporada ;-)
Tomasito665

1
Esta es una buena solución, pero parece demasiado compleja. Consulte stackoverflow.com/a/54995234/1265955 que se inspiró en este.
Victoria

6

Usando una lista de comprensión, devuelve una tupla de 3 con los elementos actual, anterior y siguiente:

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

No sé cómo no ha surgido esto todavía, ya que solo usa funciones integradas y se puede extender fácilmente a otras compensaciones:

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

Aquí hay una versión que usa generadores sin errores de límites:

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

Tenga en cuenta que el comportamiento de los límites es que nunca buscamos "foo" en el primer o último elemento, a diferencia de su código. Nuevamente, la semántica de los límites es extraña ... y es difícil de comprender a partir de su código :)


2

usando expresiones condicionales para concisión para 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

Para cualquiera que busque una solución a esto y también quiera alternar los elementos, a continuación podría funcionar:

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)

subrayado en el último next_? No se puede editar - "debe ser al menos 6 ..."
Xpector

¿Por qué esto es preferible de alguna manera a un simple bucle for y acceso objects[i-1], objects[i], objects[i+1]? o un generador? Simplemente me parece totalmente oscurantista. También utiliza innecesariamente la memoria 3x ya que PREV y NEXT hacen copias de los datos.
smci

@smci ¿Cómo logras que el i+1enfoque funcione para el último elemento de la lista? El siguiente elemento debería ser el primero. Me salgo de los límites.
ElectRocnic

1

Usando generadores, es bastante 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 )

Tenga en cuenta que las pruebas que incluyen None y el mismo valor que el valor de la señal aún funcionan, porque la verificación del valor de la señal usa "es" y la señal es un valor que Python no realiza. Sin embargo, cualquier valor de marcador singleton puede usarse como señal, lo que podría simplificar el código de usuario en algunas circunstancias.


8
"Es bastante simple"
Miguel Stevens

No digas "señal" cuando te refieres a "centinela". Además, nunca use if iB is signalpara comparar objetos por igualdad, a menos que signal = None, en cuyo caso simplemente escriba directamente Noneya. No lo use itercomo nombre de argumento, ya que ensombrece el incorporado iter(). Ídem next. De todos modos, el enfoque del generador puede ser simplementeyield prev, curr, next_
smci

@smci Quizás tu diccionario tenga definiciones distintas a las mías, en cuanto a señal y centinela. Usé específicamente "es" porque quería probar para el elemento específico, no para otros elementos con el mismo valor, "es" es el operador correcto para esa prueba. El uso de iter y next shadow solo cosas a las que no se hace referencia de otro modo, por lo que no es un problema, pero está de acuerdo, no es la mejor práctica. Debe mostrar más código para proporcionar contexto para su último reclamo.
Victoria

@Victoria: 'sentinel [valor]' es un término de software bien definido, 'señal' no lo es (sin hablar de procesamiento de señales o señales del kernel). En cuanto a [comparar cosas en Python con en islugar de ==], es una trampa bien conocida, aquí hay varias razones por las que: puede salirse con la suya para las cadenas, porque está confiando en las cadenas de internación de cPython, pero incluso entonces v1 = 'monkey'; v2 = 'mon'; v3 = 'key, entonces v1 is (v2 + v3)da False. Y si su código alguna vez cambia a usar objetos en lugar de ints / strings, el uso isse romperá. Entonces, en general, debe usar ==para comparar igualdad.
smci

@smci El problema más difícil en el software de computadora son las comunicaciones, las redes no funcionan debido a que diferentes grupos de personas usan términos diferentes. Como dice el refrán, los estándares son geniales, todo el mundo tiene uno. Entiendo completamente la diferencia entre Python == y los operadores is, y es exactamente por eso que elegí usar is. Si mira más allá de su "terminología y reglas" preconcebidas, se daría cuenta de que == permitiría que cualquier elemento que se compare igual termine la secuencia, mientras que el uso solo terminará en el objeto específico que se está utilizando como señal (o centinela si lo prefiere).
Victoria

1

Dos sencillas soluciones:

  1. Si es necesario definir variables para los valores anteriores y siguientes:
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 todos los valores de la lista deben ser recorridos por la variable de valor actual:
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

Puede usar indexen la lista para encontrar dónde somevalueestá y luego obtener el anterior y el siguiente según sea necesario:


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, esto debería ser bastante rápido, pero no lo probé:

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

Uso de ejemplo:

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

Creo que esto funciona y no es complicado.

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]

Poco sabía que Python admite índices negativos en matrices, ¡gracias!
ElectRocnic

1
Sin embargo, fallará al final :(
ElectRocnic

0

Solución de estilo muy 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

Manera pitónica y 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
Fallará si valueestá al final. Además, devuelve el último elemento como previous_valuesi fuera valueel primero.
Trang Oul

Depende de sus requisitos. El índice negativo de previous_value devolverá el último elemento de la lista y next_valueaumentará IndexErrory eso es un error
ImportError

He estado buscando este método durante bastante tiempo ... ahora lo entiendo ... gracias @ImportError. Ahora puedo expandir mi guión muy bien ..
Azam

Limitación: valuepodría ocurrir más de una vez objects, pero el uso .index()solo encontrará su primera ocurrencia (o ValueError si no ocurre).
smci
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.