167

I sometimes need to iterate a list in Python looking at the "current" element and the "next" element. I have, till now, done so with code like:

for current, next in zip(the_list, the_list[1:]):
    # Do something

This works and does what I expect, but is there's a more idiomatic or efficient way to do the same thing?

Peter Mortensen
  • 30,030
  • 21
  • 100
  • 124
dcrosta
  • 24,905
  • 8
  • 68
  • 82
  • 2
    Take a look at [Build a Basic Python Iterator](http://stackoverflow.com/questions/19151/build-a-basic-python-iterator). – mkluwe Mar 25 '11 at 16:08
  • 49
    since no one else has mentioned it, I'll be that guy, and point out that using `next` this way masks a built-in. – senderle Mar 27 '11 at 14:53
  • Check MizardX answer for [this question](http://stackoverflow.com/questions/323750/how-to-access-previous-next-element-while-for-looping). But i don't think this solution is more idiomatic than yours. – Fábio Diniz Mar 25 '11 at 16:00

12 Answers12

166

Here's a relevant example from the itertools module docs:

import itertools
def pairwise(iterable):
    "s -> (s0, s1), (s1, s2), (s2, s3), ..."
    a, b = itertools.tee(iterable)
    next(b, None)
    return zip(a, b)   

For Python 2, you need itertools.izip instead of zip:

import itertools
def pairwise(iterable):
    "s -> (s0, s1), (s1, s2), (s2, s3), ..."
    a, b = itertools.tee(iterable)
    next(b, None)
    return itertools.izip(a, b)

How this works:

First, two parallel iterators, a and b are created (the tee() call), both pointing to the first element of the original iterable. The second iterator, b is moved 1 step forward (the next(b, None)) call). At this point a points to s0 and b points to s1. Both a and b can traverse the original iterator independently - the izip function takes the two iterators and makes pairs of the returned elements, advancing both iterators at the same pace.

One caveat: the tee() function produces two iterators that can advance independently of each other, but it comes at a cost. If one of the iterators advances further than the other, then tee() needs to keep the consumed elements in memory until the second iterator comsumes them too (it cannot 'rewind' the original iterator). Here it doesn't matter because one iterator is only 1 step ahead of the other, but in general it's easy to use a lot of memory this way.

And since tee() can take an n parameter, this can also be used for more than two parallel iterators:

def threes(iterator):
    "s -> (s0, s1, s2), (s1, s2, s3), (s2, s3, 4), ..."
    a, b, c = itertools.tee(iterator, 3)
    next(b, None)
    next(c, None)
    next(c, None)
    return zip(a, b, c)
Rafał Dowgird
  • 40,680
  • 11
  • 76
  • 89
  • 10
    `zip(ł, ł[1:])` is much shorter and pythonic – noɥʇʎԀʎzɐɹƆ Jul 16 '16 at 17:18
  • 9
    @noɥʇʎԀʎzɐɹƆ: No, it doesn't work on every iterable and makes an unnecessary copy when used on lists. Using functions is pythonic. – Ry- Oct 24 '17 at 16:13
  • This function implemented in `funcy` module: `funcy.pairwise`: https://funcy.readthedocs.io/en/stable/seqs.html#pairwise – ADR Jan 13 '18 at 13:50
  • Note: As of 3.10, [`pairwise` is provided directly in `itertools`](https://docs.python.org/3/library/itertools.html#itertools.pairwise) (equivalent to the `pairwise` recipe, but pushed completely to the C layer, making it faster on the CPython reference interpreter). – ShadowRanger Apr 18 '22 at 23:05
  • Note that a fully general `windowed` recipe can be made by combining the `consume` recipe with your `threes`, by replacing the copy-pasted calls to `next` with a simple loop (done without unpacking the result of `tee`): `teed_iters = itertools.tee(iterator, n)`, `for i, it in enumerate(teed_iters): consume(it, i)`, `return zip(*teed_iters)`. – ShadowRanger Apr 18 '22 at 23:09
49

Roll your own!

def pairwise(iterable):
    it = iter(iterable)
    a = next(it, None)

    for b in it:
        yield (a, b)
        a = b
Ry-
  • 209,133
  • 54
  • 439
  • 449
  • 2
    Just what I needed! Has this been immortalized as a python method, or do we need to keep rolling? – uhoh Jan 20 '18 at 23:45
  • 1
    @uhoh: Hasn’t yet as far as I know! – Ry- Jan 20 '18 at 23:46
  • I'm surprised this is not the accepted answer. No imports and the logic behind it is very easy to understand. +1 definitely. – salfaris Aug 17 '20 at 04:04
  • 8
    It will soon be included as [`itertools.pairwise`](https://docs.python.org/3.10/library/itertools.html#itertools.pairwise) in 3.10 ! – CrazyChucky Apr 23 '21 at 01:31
27

I’m just putting this out, I’m very surprised no one has thought of enumerate().

for (index, thing) in enumerate(the_list):
    if index < len(the_list):
        current, next_ = thing, the_list[index + 1]
        #do something
Quintec
  • 974
  • 10
  • 22
  • 15
    Actually, the `if` can also be removed if you use slicing: `for (index, thing) in enumerate(the_list[:-1]): current, next_ = thing, the_list[index + 1]` – lifebalance Mar 25 '15 at 05:05
  • 4
    This should really be the answer, it doesn't rely on any extra imports and works great. – jamescampbell Sep 27 '18 at 13:09
  • 4
    Though, it does not work for non-indexable iterables so it's not a generic solution. – wim Apr 20 '20 at 20:05
23

Since the_list[1:] actually creates a copy of the whole list (excluding its first element), and zip() creates a list of tuples immediately when called, in total three copies of your list are created. If your list is very large, you might prefer

from itertools import izip, islice
for current_item, next_item in izip(the_list, islice(the_list, 1, None)):
    print(current_item, next_item)

which does not copy the list at all.

Bengt
  • 13,291
  • 6
  • 46
  • 65
Sven Marnach
  • 530,615
  • 113
  • 910
  • 808
  • 4
    note that in python 3.x izip is suppressed of itertools and you should use builtin zip – Xavier Combelle Mar 25 '11 at 16:04
  • 1
    Actually, doesn't `the_list[1:]` just create a slice object rather than a copy of almost the whole list -- so the OP's technique isn't quite as wasteful as you make it sound. – martineau Mar 25 '11 at 16:14
  • 3
    I think `[1:]` creates the slice object (or possibly "`1:`"), which is passed to `__slice__` on the list, which then returns a copy containing only the selected elements. One idiomatic way to copy a list is `l_copy = l[:]` (which I find ugly and unreadable -- prefer `l_copy = list(l)`) – dcrosta Mar 25 '11 at 16:16
  • @dcrosta: I think you're right about the `1:` being the slice object, so I stand corrected. – martineau Mar 25 '11 at 16:20
  • 4
    @dcrosta: There is no `__slice__` special method. `the_list[1:]` is equivalent to `the_list[slice(1, None)]`, which in turn is equivalent to `list.__getitem__(the_list, slice(1, None))`. – Sven Marnach Mar 25 '11 at 16:47
  • @sven OK -- good point, and thanks for correcting. I don't know all the magic methods that well... – dcrosta Mar 25 '11 at 16:52
  • 4
    @martineau: The copy created by `the_list[1:]` is only a shallow copy, so it consists only of one pointer per list item. The more memory intensive part is the `zip()` itself, because it will create a list of one `tuple` instance per list item, each of which will contain two pointers to the two items and some additional information. This list will consume nine times the amount of memory the copy caused by `[1:]` consumes. – Sven Marnach Mar 25 '11 at 17:01
  • 1
    This also doesn't work if "the_list" is an iterator, since both izip and islice would consume the same one. – miracle2k Oct 14 '13 at 22:51
21

Starting in Python 3.10, this is the exact role of the pairwise function:

from itertools import pairwise

list(pairwise([1, 2, 3, 4, 5]))
# [(1, 2), (2, 3), (3, 4), (4, 5)]

or simply pairwise([1, 2, 3, 4, 5]) if you don't need the result as a list.

Ry-
  • 209,133
  • 54
  • 439
  • 449
Xavier Guihot
  • 43,847
  • 17
  • 251
  • 159
16

Iterating by index can do the same thing:

#!/usr/bin/python
the_list = [1, 2, 3, 4]
for i in xrange(len(the_list) - 1):
    current_item, next_item = the_list[i], the_list[i + 1]
    print(current_item, next_item)

Output:

(1, 2)
(2, 3)
(3, 4)
Bengt
  • 13,291
  • 6
  • 46
  • 65
Rumple Stiltskin
  • 8,559
  • 1
  • 19
  • 24
  • Your answer was more *previous* and *current* instead of *current* and *next*, as in the question. I made an edit improving the semantics so that `i` is always the index of the current element. – Bengt Sep 24 '12 at 13:14
4

I am really surprised nobody has mentioned the shorter, simpler and most importantly general solution:

Python 3:

from itertools import islice

def n_wise(iterable, n):
    return zip(*(islice(iterable, i, None) for i in range(n)))

Python 2:

from itertools import izip, islice

def n_wise(iterable, n):
    return izip(*(islice(iterable, i, None) for i in xrange(n)))

It works for pairwise iteration by passing n=2, but can handle any higher number:

>>> for a, b in n_wise('Hello!', 2):
>>>     print(a, b)
H e
e l
l l
l o
o !

>>> for a, b, c, d in n_wise('Hello World!', 4):
>>>     print(a, b, c, d)
H e l l
e l l o
l l o
l o   W
o   W o
  W o r
W o r l
o r l d
r l d !
Marco Bonelli
  • 55,971
  • 20
  • 106
  • 115
4

This is now a simple Import As of 16th May 2020

from more_itertools import pairwise
for current, next in pairwise(your_iterable):
  print(f'Current = {current}, next = {nxt}')

Docs for more-itertools Under the hood this code is the same as that in the other answers, but I much prefer imports when available.

If you don't already have it installed then: pip install more-itertools

Example

For instance if you had the fibbonnacci sequence, you could calculate the ratios of subsequent pairs as:

from more_itertools import pairwise
fib= [1,1,2,3,5,8,13]
for current, nxt in pairwise(fib):
    ratio=current/nxt
    print(f'Curent = {current}, next = {nxt}, ratio = {ratio} ')
jabberwocky
  • 909
  • 7
  • 17
2

As others have pointed out, itertools.pairwise() is the way to go on recent versions of Python. However, for 3.8+, a fun and somewhat more concise (compared to the other solutions that have been posted) option that does not require an extra import comes via the walrus operator:

def pairwise(iterable):
  a = next(iterable)
  yield from ((a, a := b) for b in iterable)
nth
  • 1,250
  • 14
  • 12
0

Pairs from a list using a list comprehension

the_list = [1, 2, 3, 4]
pairs = [[the_list[i], the_list[i + 1]] for i in range(len(the_list) - 1)]
for [current_item, next_item] in pairs:
    print(current_item, next_item)

Output:

(1, 2)
(2, 3)
(3, 4)
Bengt
  • 13,291
  • 6
  • 46
  • 65
0

A basic solution:

def neighbors( list ):
  i = 0
  while i + 1 < len( list ):
    yield ( list[ i ], list[ i + 1 ] )
    i += 1

for ( x, y ) in neighbors( list ):
  print( x, y )
mkluwe
  • 3,483
  • 1
  • 25
  • 44
-1
code = '0016364ee0942aa7cc04a8189ef3'
# Getting the current and next item
print  [code[idx]+code[idx+1] for idx in range(len(code)-1)]
# Getting the pair
print  [code[idx*2]+code[idx*2+1] for idx in range(len(code)/2)]
Russell Wong
  • 103
  • 1
  • 4