51

Is there a way to access a list's (or tuple's, or other iterable's) next or previous element while looping through it with a for loop?

l = [1, 2, 3]
for item in l:
    if item == 2:
        get_previous(l, item)
Boris Verkhovskiy
  • 14,854
  • 11
  • 100
  • 103
Bartosz Radaczyński
  • 18,396
  • 14
  • 54
  • 61

15 Answers15

78

Expressed as a generator function:

def neighborhood(iterable):
    iterator = iter(iterable)
    prev_item = None
    current_item = next(iterator)  # throws StopIteration if empty.
    for next_item in iterator:
        yield (prev_item, current_item, next_item)
        prev_item = current_item
        current_item = next_item
    yield (prev_item, current_item, None)

Usage:

for prev,item,next in neighborhood(l):
    print prev, item, next
Markus Jarderot
  • 86,735
  • 21
  • 136
  • 138
  • 1
    I might do "prev, item = item, next" in this case. – Paul Fisher Nov 27 '08 at 17:31
  • 1
    To make this cycle infinitely (no StopIteration), do `from itertools import cycle` and change the second line to: `iterator = cycle(iterable)` – Dennis Williamson Dec 17 '09 at 01:50
  • Is it less Pythonic to use enumerate in this context? – batbrat Feb 27 '14 at 06:40
  • The answer you're looking for is Vicky Liau's, the one below this one. Or, if you want it to work on any iterable and not just lists/tuples/strs etc, the [one using `itertools`](https://stackoverflow.com/a/41047005/3064538). – Boris Verkhovskiy Dec 28 '19 at 10:04
  • Just do `for prev, cur, nxt in zip(l, l[1:], l[2:]):` instead of all this code. If you need to set `prev` and `nxt` to `None` for the first and last iteration, just do `l = [None, *l, None]` before the `for` loop. – Boris Verkhovskiy Dec 28 '19 at 17:12
  • Try to use this on an empty list causes a [`RuntimeError`](https://docs.python.org/library/exceptions.html#RuntimeError) (because it throws a [`StopIteration`](https://docs.python.org/library/exceptions.html#StopIteration) error). – Boris Verkhovskiy Dec 28 '19 at 17:26
  • Throwing `StopIteration` is normal and expected for a generator function. When called in a `for`-loop, it will just terminate the loop. – Markus Jarderot Dec 29 '19 at 01:25
  • @Boris "below this one" is not very precise because with the change in score of each answer they might go up or down. Were you talking about the answer from Vicky Lau? – Jean-Francois T. Apr 09 '20 at 15:45
  • @Jean-FrancoisT. "The answer you're looking for is Vicky Liau's" – Boris Verkhovskiy Apr 09 '20 at 15:50
  • @Boris, worth noting though that your solution doesn't keep generators as generators - `l = [None, *l, None]` unpacks `l` into a list. The original solution keeps generators as generators, which might be what you want, e.g. for memory or side effects. – Sean May 16 '20 at 09:20
  • 1
    For python 3.10 - users there is ready to go function itertools.pairwise, see https://stackoverflow.com/a/69584524/3361462 – kosciej16 Oct 15 '21 at 12:14
35
l = [1, 2, 3]

for i, j in zip(l, l[1:]):
    print(i, j)
Boris Verkhovskiy
  • 14,854
  • 11
  • 100
  • 103
Vicky Liau
  • 615
  • 1
  • 9
  • 21
  • 14
    I used this, but expanded to avoid dropping the start/end items: `for prev,cur,next in zip([None]+l[:-1], l, l[1:]+[None]):` – Maximus Sep 24 '14 at 02:35
  • You can avoid dropping the start/end items like this: `l = [None, *l, None]` and then `for prev, cur, nxt in zip(l, l[1:], l[2:]):` – Boris Verkhovskiy Dec 28 '19 at 17:14
12
l = [1, 2, 3]
for i, item in enumerate(l):
    if item == 2:
        previous = l[i - 1]
        print(previous)

Output:

1

This will wrap around and return the last item in the list if the item you're looking for is the first item in list. In other words changing the third line to if item == 1: in the above code will cause it to print 3.

Boris Verkhovskiy
  • 14,854
  • 11
  • 100
  • 103
Rudiger Wolf
  • 1,760
  • 12
  • 15
10

When dealing with generators where you need some context, I often use the below utility function to give a sliding window view on an iterator:

import collections, itertools

def window(it, winsize, step=1):
    """Sliding window iterator."""
    it=iter(it)  # Ensure we have an iterator
    l=collections.deque(itertools.islice(it, winsize))
    while 1:  # Continue till StopIteration gets raised.
        yield tuple(l)
        for i in range(step):
            l.append(it.next())
            l.popleft()

It'll generate a view of the sequence N items at a time, shifting step places over. eg.

>>> list(window([1,2,3,4,5],3))
[(1, 2, 3), (2, 3, 4), (3, 4, 5)]

When using in lookahead/behind situations where you also need to deal with numbers without having a next or previous value, you may want pad the sequence with an appropriate value such as None.

l= range(10)
# Print adjacent numbers
for cur, next in window(l + [None] ,2):
    if next is None: print "%d is the last number." % cur
    else: print "%d is followed by %d" % (cur,next)
Brian
  • 116,865
  • 28
  • 107
  • 112
  • If you're using `itertools`, use Francisco Couzo's answer with [`itertools.tee`](https://docs.python.org/library/itertools.html#itertools.tee) instead: https://stackoverflow.com/a/41047005 – Boris Verkhovskiy Apr 09 '20 at 20:59
8

I know this is old, but why not just use enumerate?

l = ['adam', 'rick', 'morty', 'adam', 'billy', 'bob', 'wally', 'bob', 'jerry']

for i, item in enumerate(l):
    if i == 0:
        previous_item = None
    else:
        previous_item = l[i - 1]

    if i == len(l) - 1:
        next_item = None
    else:
        next_item = l[i + 1]

    print('Previous Item:', previous_item)
    print('Item:', item)
    print('Next Item:', next_item)
    print('')

    pass

If you run this you will see that it grabs previous and next items and doesn't care about repeating items in the list.

RattleyCooper
  • 4,997
  • 5
  • 27
  • 43
5

Check out the looper utility from the Tempita project. It gives you a wrapper object around the loop item that provides properties such as previous, next, first, last etc.

Take a look at the source code for the looper class, it is quite simple. There are other such loop helpers out there, but I cannot remember any others right now.

Example:

> easy_install Tempita
> python
>>> from tempita import looper
>>> for loop, i in looper([1, 2, 3]):
...     print loop.previous, loop.item, loop.index, loop.next, loop.first, loop.last, loop.length, loop.odd, loop.even
... 
None 1 0 2 True False 3 True 0
1 2 1 3 False False 3 False 1
2 3 2 None False True 3 True 0
codeape
  • 97,830
  • 24
  • 159
  • 188
3

If you want the solution to work on iterables, the itertools documentation has a recipe that does exactly what you want using itertools.tee():

import itertools

def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = itertools.tee(iterable)
    next(b, None)
    return zip(a, b)
Boris Verkhovskiy
  • 14,854
  • 11
  • 100
  • 103
Francisco
  • 10,918
  • 6
  • 34
  • 45
2

If you don't want to import anything, here is an example of accessing the previous item of a generator with the for loop. It uses a class variable to store each next result before the following next call. This variable could be a small list if you wanted more than the immediately previous item. Inside the class is a method generator that effectively extends the next() builtin to include the previous item assignment.

Code (Python 3.10):

def previous():
    class Plusprev():
        def __init__(pp, gen=None):
            pp.g = gen
            pp.nxt = ''
            pp.prev = 'start'

        def ppnext(pp):
            while pp.nxt != 'done':
                pp.nxt = next(pp.g,'done')
                yield pp.nxt
                pp.prev = pp.nxt

    sqgen = (n*n for n in range(13))
    ppcl = Plusprev(sqgen)
    nxtg = ppcl.ppnext()
    nxt = next(nxtg,'done')
    while nxt != 'done':
        print('\nprevious ',ppcl.prev)
        print('current ',nxt)
        nxt = next(nxtg,'done')

previous()

This uses the builtin function, next(), default parameter.

InhirCode
  • 338
  • 1
  • 4
  • 5
1

I don't think there is a straightforward way, especially because an iterable can be a generator (no going back). You can do it with sequences by passing the index of the element into the loop body:

for index, item in enumerate(l):
    if index > 0:
        previous_item = l[index - 1]
    else:
        previous_item = None 

The enumerate() function is a builtin.

Boris Verkhovskiy
  • 14,854
  • 11
  • 100
  • 103
Rafał Dowgird
  • 43,216
  • 11
  • 77
  • 90
1

Immediately previous?

You mean the following, right?

previous = None
for item in someList:
    if item == target: break
    previous = item
# previous is the item before the target

If you want n previous items, you can do this with a kind of circular queue of size n.

queue = []
for item in someList:
    if item == target: break
    queue .append( item )
    if len(queue ) > n: queue .pop(0)
if len(queue ) < n: previous = None
previous = previous[0]
# previous is *n* before the target
S.Lott
  • 384,516
  • 81
  • 508
  • 779
1

I know this is an old question but I find it important to show a simple solution which works also with generators and other kind of iterables, not like most of the answers working only with list like objects. It is somewhat similar to Brian's answer and the solution here: https://www.programcreek.com/python/example/1754/itertools.tee

import itertools

iter0, iter1 = itertools.tee(iterable)

for item, next_item in itertools.zip_longest(
    iter0,
    itertools.islice(iter1, 1, None)
):

    do_something(item, next_item)

Alternatively, calling next on the second iterable (if you are sure it has at least one element):

import itertools

iter0, iter1 = itertools.tee(iterable)
_ = next(iter1)

for item, next_item in itertools.zip_longest(iter0, iter1):

    do_something(item, next_item)
deeenes
  • 4,148
  • 5
  • 43
  • 59
1

For anyone who upgraded to python 3.10, such function was added directly to itertools

import itertools

l = [1,2,3]
for x, y in itertools.pairwise(l):
    print(x, y)
# 1 2
# 2 3
kosciej16
  • 6,294
  • 1
  • 18
  • 29
0

Iterators only have the next() method so you cannot look forwards or backwards, you can only get the next item.

enumerate(iterable) can be useful if you are iterating a list or tuple.

chirag
  • 67
  • 6
-2

Not very pythonic, but gets it done and is simple:

l=[1,2,3]
for index in range(len(l)):
    if l[index]==2:
        l[index-1]

TO DO: protect the edges

Emilio M Bumachar
  • 2,532
  • 3
  • 26
  • 30
-7

The most simple way is to search the list for the item:

def get_previous(l, item):
    idx = l.find(item)
    return None if idx == 0 else l[idx-1]

Of course, this only works if the list only contains unique items. The other solution is:

for idx in range(len(l)):
    item = l[idx]
    if item == 2:
        l[idx-1]
Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820