95
votes

How can I iterate over a list of objects, accessing the previous, current, and next items? Like this C/C++ code, in 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];
    }
}
15
What should happen if foo is at the start or end of the list? Currently, this will go out of the bounds of your array. - Brian
if you need the first occurrence of "foo", then do "break" from the "for" block when matched. - van
Do you want to start iterating at the 1'st (not 0'th) element, and end iterating at the last-but-one element? - smci
Is it guaranteed that foo occurs exactly once in the list? If it occurs multiply, some approaches here will fail, or only find the first. And if it never occurs, other approaches will fail, or throw exceptions like ValueError. Giving some testcases would have helped. - smci
Also, your example here is a sequence of objects, which both has a known length, and is indexable. Some of the answers here are generalizing to iterators, which are not always indexable, don’t always have lengths, and are not always finite.. - smci

15 Answers

114
votes

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.

153
votes

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

7
votes

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

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

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

2
votes

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

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

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

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.

1
votes

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

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'


0
votes

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

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

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]
-1
votes

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