Loop that also accesses previous and next values

PythonLoopsIteration

Python Problem Overview


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];
    }
}

Python Solutions


Solution 1 - Python

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

Solution 2 - Python

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.

Solution 3 - Python

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

Solution 4 - Python

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)

Solution 5 - Python

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

Solution 6 - Python

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

Solution 7 - Python

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)


Solution 8 - Python

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)

Solution 9 - Python

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

Solution 10 - Python

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.

Solution 11 - Python

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'





Solution 12 - Python

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

Solution 13 - Python

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

Solution 14 - Python

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]

Solution 15 - Python

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questiondir01View Question on Stackoverflow
Solution 1 - PythonnoskloView Answer on Stackoverflow
Solution 2 - PythonHank GayView Answer on Stackoverflow
Solution 3 - PythonRYSView Answer on Stackoverflow
Solution 4 - PythonEric CzechView Answer on Stackoverflow
Solution 5 - PythonBoris VerkhovskiyView Answer on Stackoverflow
Solution 6 - PythonmoshezView Answer on Stackoverflow
Solution 7 - PythonmakapufView Answer on Stackoverflow
Solution 8 - Pythonskr47chView Answer on Stackoverflow
Solution 9 - PythonSerge TochilovView Answer on Stackoverflow
Solution 10 - PythonVictoriaView Answer on Stackoverflow
Solution 11 - PythonJohn MontgomeryView Answer on Stackoverflow
Solution 12 - PythonSfisolView Answer on Stackoverflow
Solution 13 - PythonImportErrorView Answer on Stackoverflow
Solution 14 - PythonSoda FriesView Answer on Stackoverflow
Solution 15 - PythonMarcelo AzambujaView Answer on Stackoverflow