190

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?


Some answers to this problem can simplify by addressing the specific case of taking only two elements at a time. For the general case of N elements at a time, see Rolling or sliding window iterator?.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
dcrosta
  • 26,009
  • 8
  • 71
  • 83
  • 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
  • 56
    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

13 Answers13

176

The documentation for 3.8 provides this recipe:

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, use itertools.izip instead of zip to get the same kind of lazy iterator (zip will instead create a list):

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.

Since tee() can take an n parameter (the number of iterators to produce), the same technique can be adapted to produce a larger "window". For example:

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)

Caveat: If one of the iterators produced by tee advances further than the others, then the implementation needs to keep the consumed elements in memory until every iterator has consumed them (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.

Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
Rafał Dowgird
  • 43,216
  • 11
  • 77
  • 90
  • 12
    `zip(ł, ł[1:])` is much shorter and pythonic – noɥʇʎԀʎzɐɹƆ Jul 16 '16 at 17:18
  • 12
    @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
  • 6
    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
  • If you need the interval between indexes you can use this list comprehension: a = [1, 9, 15, 25]; d = [aa-bb for aa,bb in zip(a[1:], a[:-1])]; print(d) => [8, 6, 10] # i.e. 9-1, 15-9, and 25-15 – Steve L Jun 07 '23 at 21:59
57

Roll your own!

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

    for b in it:
        yield (a, b)
        a = b
Ry-
  • 218,210
  • 55
  • 464
  • 476
  • 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
  • 2
    @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. – saru Aug 17 '20 at 04:04
  • 9
    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
  • And add `yield(a, None)` if you also wish to get the last item. – caram Mar 07 '23 at 08:22
43

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-
  • 218,210
  • 55
  • 464
  • 476
Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
31

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
  • 1,064
  • 12
  • 22
24

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
  • 14,011
  • 7
  • 48
  • 66
Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • 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
  • 2
    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
  • 5
    @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
17

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
  • 14,011
  • 7
  • 48
  • 66
Rumple Stiltskin
  • 9,597
  • 1
  • 20
  • 25
  • 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
5

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
  • 63,369
  • 21
  • 118
  • 128
  • We have a canonical duplicate for the general version: [Rolling or sliding window iterator?](https://stackoverflow.com/questions/6822725/). It does appear that that question is slightly newer. – Karl Knechtel Aug 11 '22 at 07:15
3

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
  • 995
  • 10
  • 18
  • 4
    "This is now a simple Import" - well no, not really, as `more_itertools` is an external package that needs to be installed... – Marco Bonelli Jun 27 '22 at 12:03
  • True, but at the same time more-itertools such an incredibly useful library that I recommend most Python programmers at least look into it. – CrazyChucky Jan 06 '23 at 02:48
3

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,442
  • 15
  • 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
  • 14,011
  • 7
  • 48
  • 66
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,823
  • 2
  • 28
  • 45
0
temp=[-39.5, -27.5, -15.5, -3.5, 8.5, 20.5, 32.5, 44.5, 56.5, 68.5, 80.5, 92.5,104.5]

li_tup = []
count=0
i=0
new_tup=()
while i<len(temp):
    if count<1:
        new_tup = new_tup + (temp[i],)
        count=count+1
        i=i+1
    else:
        new_tup = new_tup + (temp[i],)
        count=0
        li_tup.append(new_tup)
        new_tup=()
print(li_tup)

One way to do it would be as above

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jun 07 '23 at 22:15
-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