Loop Python que também acessa os valores anteriores e seguintes


91

Como posso iterar em uma lista de objetos, acessando os itens anteriores, atuais e próximos? Gosta desse código C / C ++, em Python?


O que deve acontecer se foo estiver no início ou no final da lista? Atualmente, isso sairá dos limites do seu array.
Brian de

2
se você precisar da primeira ocorrência de "foo", faça "break" no bloco "for" quando houver correspondência.
van de

Você deseja iniciar a iteração no primeiro (não no 0 °) elemento e terminar a iteração no penúltimo elemento?
smci de

É garantido que fooocorre exatamente uma vez na lista? Se ocorrer multiplicação, algumas abordagens aqui falharão ou apenas encontrarão a primeira. E se isso nunca ocorrer, outras abordagens falharão ou lançarão exceções como ValueError. Dar alguns casos de teste teria ajudado.
smci de

Além disso, seu exemplo aqui é uma sequência de objetos, que têm um comprimento conhecido e são indexáveis. Algumas das respostas aqui são generalizantes para iteradores, que nem sempre são indexáveis, nem sempre têm comprimentos e nem sempre são finitos.
smci

Respostas:


111

Isso deve 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]

Aqui estão os documentos sobre a enumeratefunção.


18
Mas provavelmente a melhor prática é não usar 'próximo' como nome de variável, pois é uma função embutida.
mkosmala 01 de

1
A versão editada disso ainda não é logicamente sólida: no final do loop obje next_será o mesmo objeto da última iteração, o que pode ter efeitos colaterais indesejados.
TemporalWolf

A instrução question diz explicitamente que o OP deseja iniciar a iteração no 1º (não no 0º) elemento e terminar a iteração no penúltimo elemento. Portanto, indexdeve ser executado 1 ... (l-1), não 0 ... lcomo você fez aqui, e não há necessidade de cláusulas if com letras especiais. Btw, há um parâmetro, enumerate(..., start=1)mas não para end. Portanto, não queremos realmente usar enumerate().
smci de

147

As soluções até agora lidam apenas com listas, e a maioria está copiando a lista. Na minha experiência, muitas vezes isso não é possível.

Além disso, eles não lidam com o fato de que você pode ter elementos repetidos na lista.

O título da sua pergunta diz " Valores anteriores e próximos dentro de um loop ", mas se você executar a maioria das respostas aqui dentro de um loop, acabará iterando a lista inteira novamente em cada elemento para encontrá-lo.

Acabei de criar uma função que. usando o itertoolsmódulo, divide e divide o iterável e gera tuplas com os elementos anteriores e seguintes juntos. Não é exatamente o que seu código faz, mas vale a pena dar uma olhada, porque provavelmente pode resolver seu 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)

Em seguida, use-o em um loop e você terá os itens anteriores e os próximos nele:

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

Os 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á com qualquer lista de tamanho (porque não copia a lista), e com qualquer iterável (arquivos, conjuntos, etc). Dessa forma, você pode apenas iterar sobre a sequência e ter os itens anteriores e posteriores disponíveis dentro do loop. Não há necessidade de pesquisar novamente o item na sequência.

Uma breve explicação do código:

  • tee é usado para criar com eficiência 3 iteradores independentes sobre a sequência de entrada
  • chainliga duas sequências em uma; é usado aqui para anexar uma sequência de elemento único [None]aprevs
  • isliceé usado para fazer uma sequência de todos os elementos exceto o primeiro, então chainé usado para adicionar a Noneao seu final
  • Existem agora 3 sequências independentes baseadas em some_iterableque se parecem com:
    • prevs: None, A, B, C, D, E
    • items: A, B, C, D, E
    • nexts: B, C, D, E, None
  • finalmente izipé usado para alterar 3 sequências em uma sequência de tripletos.

Observe que izippara quando qualquer sequência de entrada se esgota, então o último elemento de prevsserá ignorado, o que é correto - não existe tal elemento que o último elemento seria seu prev. Poderíamos tentar remover os últimos elementos, prevsmas izipo comportamento torna isso redundante

Observe também que tee, izip, islicee chainvêm do itertoolsmódulo; eles operam em suas sequências de entrada em tempo real (preguiçosamente), o que os torna eficientes e não introduz a necessidade de ter toda a sequência na memória de uma vez a qualquer momento.

Em python 3, ele mostrará um erro durante a importação izip, você pode usar em zipvez de izip. Não há necessidade de importação zip, que é pré-definido na python 3- fonte


3
@becomingGuru: não é necessário transformar o SO em um espelho dos documentos de referência do Python. Todas essas funções são muito bem explicadas (com exemplos) na documentação oficial
Eli Bendersky

1
@becomingGuru: Adicionado um link para a documentação.
nosklo de

6
@LakshmanPrasad Estou com disposição para wiki, então adicionei algumas explicações :-).
Kos

7
Vale a pena mencionar que no Python 3 izippode ser substituído pela zipfunção embutida ;-)
Tomasito665

1
Esta é uma boa solução, mas parece excessivamente complexa. Consulte stackoverflow.com/a/54995234/1265955 que foi inspirado neste.
Victoria

6

Usando uma compreensão de lista, retorne uma tupla de 3 com os elementos atual, anterior e seguinte:

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

Não sei como isso ainda não surgiu, pois usa apenas funções integradas e pode ser facilmente estendido para outros deslocamentos:

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

Esta é uma versão que usa geradores sem erros 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))

Observe que o comportamento do limite é que nunca procuramos "foo" no primeiro ou no último elemento, ao contrário do seu código. Mais uma vez, a semântica de limite é estranha ... e difícil de entender em seu código :)


2

usando expressões condicionais para concisão 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 quem procura uma solução para isso e também deseja fazer um ciclo entre os elementos, abaixo pode 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)

sublinhado no último next_? Não é possível editar - "deve ser pelo menos 6 ..."
Xpector

Por que isso é preferível a um simples loop for e acesso objects[i-1], objects[i], objects[i+1]? ou um gerador? Parece totalmente obscurantista para mim. Também usa 3x a memória desnecessariamente, já que o PREV e o NEXT fazem cópias dos dados.
smci de

@smci Como você faz a i+1abordagem funcionar para o último elemento da lista? O próximo elemento deve ser o primeiro. Eu fico fora dos limites.
ElectRocnic

1

Usando geradores, é bastante simples:

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 )

Observe que os testes que incluem Nenhum e o mesmo valor do valor do sinal ainda funcionam, porque a verificação do valor do sinal usa "é" e o sinal é um valor que o Python não internaliza. Qualquer valor de marcador de singleton pode ser usado como um sinal, o que pode simplificar o código do usuário em algumas circunstâncias.


9
"É muito simples"
Miguel Stevens

Não diga 'sinal' quando você quer dizer 'sentinela'. Além disso, nunca use if iB is signalpara comparar objetos para igualdade, a menos que sinal = Nenhum, caso em que apenas escreva diretamente None. Não use itercomo um nome de argumento, pois isso obscurece o embutido iter(). Idem next. De qualquer forma, a abordagem do gerador pode ser simplesmenteyield prev, curr, next_
smci

@smci Talvez o seu dicionário tenha definições diferentes do meu, a respeito de sinal e sentinela. Usei especificamente "é" porque queria testar o item específico, não outros itens com valor igual, "é" é o operador correto para esse teste. Uso de iter e next shadow apenas coisas que não são referenciadas de outra forma, portanto não é um problema, mas está de acordo, não é a melhor prática. Você precisa mostrar mais código para fornecer contexto para sua última reivindicação.
Victoria de

@Victoria: 'sentinel [valor]' é um termo de software bem definido, 'sinal' não é (sem falar sobre processamento de sinal ou sinais de kernel). Quanto a [comparar as coisas em Python com em isvez de ==], é uma armadilha bem conhecida, aqui estão várias razões: você pode se safar com as strings, porque está contando com strings internas do cPython, mas mesmo assim v1 = 'monkey'; v2 = 'mon'; v3 = 'key, então v1 is (v2 + v3)False. E se seu código mudar para o uso de objetos em vez de ints / strings, o uso isserá interrompido. Portanto, em geral, você deve usar ==para comparar igualdade.
smci

@smci O problema mais difícil em software de computador são as comunicações, a rede não funciona devido a diferentes grupos de pessoas usando termos diferentes. Como diz o ditado, os padrões são ótimos, todo mundo tem um. Eu entendo perfeitamente a diferença entre os operadores Python == e is, e é exatamente por isso que escolhi usar is. Se você olhasse além de sua "terminoologia e regras" preconcebidas, perceberia que == permitiria que qualquer item que se compara igual termine a sequência, enquanto o uso de is só terminará no objeto específico que está sendo usado como o sinal (ou sentinela, se preferir).
Victoria,

1

Duas soluções simples:

  1. Se as variáveis ​​para os valores anteriores e seguintes tiverem que ser definidas:
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 todos os valores da lista tiverem que ser percorridos pela variável de valor atual:
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

Você pode apenas usar indexna lista para encontrar onde somevalueestá e, em seguida, obter o anterior e o próximo conforme necessário:


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 deve ser bem rápido, mas não testei:

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

Exemplo de uso:

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

Eu acho que isso funciona e não é 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]

Mal sabia eu que python suporta índices negativos em matrizes, obrigado!
ElectRocnic

1
No entanto, falhará no final :(
ElectRocnic

0

Solução de estilo muito 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

Forma pitônica e 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
Ele irá falhar se valuefor no final. Além disso, retorna o último elemento como previous_valuese valuefosse o primeiro.
Trang Oul

Depende dos seus requisitos. O índice negativo de previous_value retornará o último elemento da lista e next_valueaumentará IndexErrore isso é erro
ImportError

Tenho procurado esse método algumas vezes ... agora entendi ... obrigado @ImportError. Agora posso expandir o meu script de forma agradável ..
Azam

Limitação: valuepode ocorrer mais de uma vez objects, mas o uso .index()só encontrará sua primeira ocorrência (ou ValueError se não ocorrer).
smci de
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.