同时访问上一个和下一个值的循环

如何迭代一个对象列表,访问上一个、当前和下一个项目?就像 Python 中的 C/C + + 代码一样?

foo = somevalue;
previous = next = 0;


for (i=1; i<objects.length(); i++) {
if (objects[i]==foo) {
previous = objects[i-1];
next = objects[i+1];
}
}
277016 次浏览

This should do the trick.

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]

Here's the docs on the enumerate function.

You could just use index on the list to find where somevalue is and then get the previous and next as needed:


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'



using conditional expressions for conciseness for 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)

Solutions until now only deal with lists, and most are copying the list. In my experience a lot of times that isn't possible.

Also, they don't deal with the fact that you can have repeated elements in the list.

The title of your question says "Previous and next values inside a loop", but if you run most answers here inside a loop, you'll end up iterating over the entire list again on each element to find it.

So I've just created a function that. using the itertools module, splits and slices the iterable, and generates tuples with the previous and next elements together. Not exactly what your code does, but it is worth taking a look, because it can probably solve your problem.

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)

Then use it in a loop, and you'll have previous and next items in it:

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

The results:

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

It'll work with any size list (because it doesn't copy the list), and with any iterable (files, sets, etc). This way you can just iterate over the sequence, and have the previous and next items available inside the loop. No need to search again for the item in the sequence.

A short explanation of the code:

  • tee is used to efficiently create 3 independent iterators over the input sequence
  • chain links two sequences into one; it's used here to append a single-element sequence [None] to prevs
  • islice is used to make a sequence of all elements except the first, then chain is used to append a None to its end
  • There are now 3 independent sequences based on some_iterable that look like:
    • prevs: None, A, B, C, D, E
    • items: A, B, C, D, E
    • nexts: B, C, D, E, None
  • finally izip is used to change 3 sequences into one sequence of triplets.

Note that izip stops when any input sequence gets exhausted, so the last element of prevs will be ignored, which is correct - there's no such element that the last element would be its prev. We could try to strip off the last elements from prevs but izip's behaviour makes that redundant

Also note that tee, izip, islice and chain come from the itertools module; they operate on their input sequences on-the-fly (lazily), which makes them efficient and doesn't introduce the need of having the whole sequence in memory at once at any time.

In Python 3 it will show an error while importing izip. You can use zip instead of izip. No need to import zip, it is predefined in Python 3 (source).

Here's a version using generators with no boundary errors:

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

Please notice that the boundary behavior is that we never look for "foo" in the first or last element, unlike your code. Again, the boundary semantics are strange...and are hard to fathom from your code :)

AFAIK this should be pretty fast, but I didn't test it:

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

Example usage:

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

Using a list comprehension, return a 3-tuple with current, previous and next elements:

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

Pythonic and elegant way:

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

Using generators, it is quite 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 )

Note that tests that include None and the same value as the signal value still work, because the check for the signal value uses "is" and the signal is a value that Python doesn't intern. Any singleton marker value can be used as a signal, though, which might simplify user code in some circumstances.

For anyone looking for a solution to this with also wanting to cycle the elements, below might work -

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)

I don't know how this hasn't come up yet since it uses only built-in functions and is easily extendable to other offsets:

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)

I think this works and not complicated

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]

Very C/C++ style solution:

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

Two simple solutions:

  1. If variables for both previous and next values have to be defined:
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. If all values in the list have to be traversed by the current value variable:
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

If you only want to iterate over elements which have a next and previous element (as in, you want to skip the first and last elements) and your input is a list, you can zip the input with itself without the first element and without the second element:

words = "one two three four five".split()


for prev, current, nxt in zip(words, words[1:], words[2:]):
print(prev, current, nxt)

Output:

one two three
two three four
three four five

If you don't want to skip the first and last elements, and want prev to be set to None when you're on the first element (and nxt to be None for the last element), pad your list with those values first:

words = "one two three four five".split()


padded_words = [None, *words, None]


for prev, current, nxt in zip(padded_words, padded_words[1:], padded_words[2:]):
print(prev, current, nxt)

Output:

None one two
one two three
two three four
three four five
four five None

You can pad with anything you'd like. If you want your list to "wrap around" (as in, the prev of the first element is the last element and the nxt of the last element is the first element), pad your input with those instead of None:

# avoid IndexError if words is an empty list
padded_words = [words[-1], *words, words[0]] if words else []

Output:

five one two
one two three
two three four
three four five
four five one

Python 3.10 introduces pairwise to itertools.

An idea based on their implementation to get the current and the two following values of an iterator:

import itertools
def triowise(iterable):
b, c = itertools.tee(iterable[1:])
next(c, None)
return zip(iterable, b, c)

If you want to access the index, be careful as it won't be the index of the middle value. Adding 1 was sufficient for my case.

An example:

>>> for n, (a, b, c) in enumerate(triowise('ABCDEFGH')):
...    n += 1
...    print('index', n, 'previous', a, 'current', b, 'next', c)


'index 1 previous A current B next C'
'index 2 previous B current C next D'
'index 3 previous C current D next E'
'index 4 previous D current E next F'
'index 5 previous E current F next G'
'index 6 previous F current G next H'

Maybe not very Pythonic, but I haven't seen anything cleaner in the answers.

def iter_in_pairs(iterable):
for i in range(1, len(iterable)):
yield (iterable[i-1], iterable[i])


test = [0, 1, 2, 3, 4, 5]
for prev, cur in iter_in_pairs(test):
print(prev, cur)


0 1
1 2
2 3
3 4
4 5