116

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];
    }
}
martineau
  • 119,623
  • 25
  • 170
  • 301
dir01
  • 2,120
  • 4
  • 18
  • 17
  • 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 Jun 18 '09 at 10:34
  • 2
    if you need the first occurrence of "foo", then do "break" from the "for" block when matched. – van Jun 18 '09 at 11:11
  • Do you want to start iterating at the 1'st (not 0'th) element, and end iterating at the last-but-one element? – smci Apr 20 '20 at 12:20
  • 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 Apr 20 '20 at 13:27
  • 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 Apr 20 '20 at 13:33
  • Your code will try to access elements past the end of the list on the last iteration. – Boris Verkhovskiy Mar 04 '21 at 05:25

17 Answers17

180

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

wovano
  • 4,543
  • 5
  • 22
  • 49
nosklo
  • 217,122
  • 57
  • 293
  • 297
  • 3
    @becomingGuru: it isn't necessary to turn SO into a mirror of the Python reference docs. All these functions are very nicely explained (with examples) in the official documentation – Eli Bendersky Jun 19 '09 at 04:52
  • 1
    @becomingGuru: Added a link to the documentation. – nosklo Jun 19 '09 at 11:13
  • 7
    @LakshmanPrasad I'm in a wiki mood so I've added some explanations :-). – Kos Jul 09 '12 at 21:39
  • I generalized this with arbitrary numbers of before and after elements https://repl.it/Ez6g/53. Thanks for the logic. – altendky Jan 03 '17 at 04:35
  • 8
    It might be worth mentioning that in Python 3 `izip` can be replaced with the built-in `zip` function ;-) – Tomasito665 Feb 21 '17 at 07:17
  • 1
    This is a nice solution, but it seems overly complex. See https://stackoverflow.com/a/54995234/1265955 which was inspired by this one. – Victoria Mar 05 '19 at 04:09
  • @Victoria although the pointed solution seems simpler, it is just unrolling what the `itertools` methods do. If you are really used to `itertools`, it seems overengineered. – nosklo Mar 05 '19 at 18:24
  • 2
    Yes, they are doing approximately the same thing. I'm not "really used to" itertools, so while I puzzled my way through your solution to figure it out, I still claim my solution is simpler, because it doesn't even require importing itertools, or all the clever mechanisms used there (which you had to explain), nor the seven extra iterators it creates. and so can be understood by others that don't "really" understand itertools either :) Your solution also assumes that None is available as a marker, but that could be trivially fixed. Yours is a great tutorial for using itertools! – Victoria Mar 06 '19 at 08:13
  • @Victoria `itertools` is part of python though - it is a pure python module that comes with python. It is like avoiding multiplication and use only addition to "*simplify*". I'd say your answer is an example on how `itertools` works internally, instead. – nosklo Mar 06 '19 at 11:21
  • 1
    I'm aware itertools is part of stdlib. My solution doesn't create 7 other iterators. I guess a real comparison of relative complexity would be to benchmark the two alternatives. My version of an arithmetic analogy would be that your solution uses integrals and derivatives instead of simple multiplication like mine does :) – Victoria Mar 06 '19 at 18:41
128

This should do the trick.

foo = somevalue
previous_item = next_item = None
l = len(objects)
for index, obj in enumerate(objects):
    if obj == foo:
        if index > 0:
            previous_item = objects[index - 1]
        if index < (l - 1):
            next_item = objects[index + 1]

Here's the docs on the enumerate function.

szymmirr
  • 27
  • 7
Hank Gay
  • 70,339
  • 36
  • 160
  • 222
  • 23
    But probably best practice not to use 'next' as your variable name, since it's a built-in function. – mkosmala Sep 01 '15 at 18:40
  • 1
    The edited version of this is still not logically sound: At the end of the loop `obj` and `next_` will be the same object for the last iteration, which may have unintended side effects. – TemporalWolf Dec 05 '17 at 22:01
  • The question statement explicitly says OP wants to start iterating at the 1'st (not 0'th) element, and end iterating at the last-but-one element. So `index` should run from `1 ... (l-1)`, not `0 ... l` as you have here, and no need for the special-cased if-clauses. Btw, there is a parameter `enumerate(..., start=1)` but not for `end`. So we don't realy want to use `enumerate()`. – smci Apr 20 '20 at 12:23
  • maybe consider having the "next" object be `None` if `index >= (l -1)`? Simply achievable with an else statement - `if index < (l - 1): .... else:`? – Shmack Mar 19 '21 at 19:03
7

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)]
RYS
  • 442
  • 6
  • 22
  • works as charm! Thanks. This works in a for loop " for c,p,n in three_tuple: then inside the loop you can access c=current,p=prev and n =nxt – bherto39 Apr 06 '21 at 17:15
7

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
Boris Verkhovskiy
  • 14,854
  • 11
  • 100
  • 103
5

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)
Eric Czech
  • 719
  • 9
  • 9
5

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'
  • i was actually wondering if there is not a built in version, thx for the heads up of pairwise! – KIC Jun 30 '22 at 10:10
4

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

wim
  • 338,267
  • 99
  • 616
  • 750
moshez
  • 36,202
  • 1
  • 22
  • 14
2

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)
makapuf
  • 1,370
  • 1
  • 13
  • 23
2

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)
skr47ch
  • 29
  • 1
  • underscore in the last next_ ? Can't edit - "must be at least 6 ..." – Xpector Nov 22 '19 at 16:29
  • Why is this in any way preferable to a simple for-loop and accessing `objects[i-1], objects[i], objects[i+1]`? or a generator? It just seems totally obscurantist to me. Also it needlessly uses 3x memory since PREV and NEXT make copies of the data. – smci Apr 20 '20 at 12:42
  • @smci How do you get the `i+1` approach working for the last element in the list? It's next element should then be the first. I get out-of-bounds. – ElectRocnic Jun 05 '20 at 13:53
2

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
Serge Tochilov
  • 191
  • 2
  • 4
2

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
Henry Gibson
  • 192
  • 1
  • 8
1

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.

Victoria
  • 497
  • 2
  • 10
  • 20
  • 13
    "It's quite simple" – Miguel Stevens Jul 27 '19 at 09:13
  • Don't say 'signal' when you mean 'sentinel'. Also, never ever use `if iB is signal` to compare objects for equality, unless signal = None, in which case just directly write `None` already. Don't use `iter` as an argument name since that shadows the builtin `iter()`. Ditto `next`. Anyway the generator approach can simply be `yield prev, curr, next_` – smci Apr 20 '20 at 13:08
  • @smci Maybe your dictionary has different definitions that mine, regarding signal and sentinel. I specifically used "is" because I wanted to test for the specific item, not for other items with equal value, "is" is the correct operator for that test. Use of iter and next shadow only things that are not otherwise referenced, so not a problem, but agreed, not the best practice. You need to show more code to provide context for your last claim. – Victoria Apr 20 '20 at 20:08
  • @Victoria: 'sentinel [value]' is well-defined software term, 'signal' isn't (not talking about signal-processing, or kernel signals). As to [comparing things in Python with `is` instead of `==`], it's a well-known pitfall, here are multiple reasons why: you can get away with it for strings, because you're relying on cPython interning strings, but even then `v1 = 'monkey'; v2 = 'mon'; v3 = 'key`, then `v1 is (v2 + v3)` gives `False`. And if your code ever switches to using objects instead of ints/strings, using `is` will break. So in general you should use `==` for comparing equality. – smci Apr 22 '20 at 09:17
  • @smci The hardest problem in computer software is communications, networking not working, due to different groups of people using different terms. As the saying goes, standards are great, everybody has one. I fully understand the difference between the Python == and is operators, and that is exactly why I chose to use is. If you'd look past your preconceived "terminoology and rules", you'd realize that == would allow any item that compares equal to terminate the sequence, whereas using is will only terminate at the spetific object that is being used as the signal (or sentinel if you prefer). – Victoria Apr 22 '20 at 19:29
0

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'


John Montgomery
  • 8,868
  • 4
  • 33
  • 43
0

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
Sfisol
  • 1
-1

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
ImportError
  • 150
  • 2
  • 9
  • 1
    It will fail if `value` is at the end. Also, returns last element as `previous_value` if `value` is the first one. – Trang Oul Apr 27 '16 at 13:24
  • Its depends on your requirements. Negative index from `previous_value` will return last element from list and `next_value` will raise `IndexError` and thats error – ImportError Apr 27 '16 at 14:18
  • I have been looking this method for quite sometimes..now I get it..thanks @ImportError. Now I can expand my script nicely.. – Azam May 17 '18 at 14:12
  • Limitation: `value` could occur more than once in `objects`, but using `.index()` will only find its first occurrence (or ValueError if it doesn't occur). – smci Apr 20 '20 at 13:26
-1

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]
Soda Fries
  • 11
  • 4
-2

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