78

Is there a nice Pythonic way to loop over a list, retuning a pair of elements? The last element should be paired with the first.

So for instance, if I have the list [1, 2, 3], I would like to get the following pairs:

  • 1 - 2
  • 2 - 3
  • 3 - 1
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
The Oddler
  • 6,314
  • 7
  • 51
  • 94
  • related [Construct a circular loop in python](http://stackoverflow.com/q/18648824) – Bhargav Rao Apr 30 '16 at 21:52
  • @BhargavRao Should probably close that one as a dupe of this one – Barry May 01 '16 at 15:22
  • @Barry The answers here are just great, Hence I did not hammer. I needed someone to reassure that the reverse dupe is better. Thanks (and I added a comment there too) – Bhargav Rao May 01 '16 at 15:24

18 Answers18

115

A Pythonic way to access a list pairwise is: zip(L, L[1:]). To connect the last item to the first one:

>>> L = [1, 2, 3]
>>> zip(L, L[1:] + L[:1])
[(1, 2), (2, 3), (3, 1)]
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • So many good answers, but this one seems nicest to me. It's very readable and concise. Thanks a lot! – The Oddler Apr 29 '16 at 07:37
  • 32
    If the list is huge, this may be inadvisable – Russia Must Remove Putin Apr 29 '16 at 12:05
  • 11
    @AaronHall correct. But remember: ["Premature optimization is the root of all evil"](http://c2.com/cgi/wiki?PrematureOptimization). If the memory is an issue or to support arbitrary iterables (not just sequences), [`itertools` solution can be used.](http://stackoverflow.com/a/36917655/4279) – jfs Apr 29 '16 at 12:16
  • I've got the itertools solution below, now. :) – Russia Must Remove Putin Apr 29 '16 at 17:29
  • 1
    Oh, I see that you're linking to a different answer. Unfortunately the answer you're linking to isn't extensible to more than 2 elements in width. I do have the direct itertools analogue to your suggestion, below, however. – Russia Must Remove Putin Apr 29 '16 at 19:24
  • @AaronHall: the word *pairwise* is in the title of the question. Some bikeshedding is expected but no need to complicate the code. – jfs Apr 29 '16 at 19:49
  • It's not that complicated - the body of the function fits on a single line. Presumably, to optimize for value creation in our answers and in code we put into a shared code-base, we would want to provide code samples/write code that can be easily extended. – Russia Must Remove Putin Apr 29 '16 at 19:53
  • 1
    @AaronHall Would it still be problematic in Python 3 versus 2.x? 3's `zip` (among other things) is pretty darn efficient. – Seldom 'Where's Monica' Needy Apr 30 '16 at 00:10
  • 5
    @SeldomNeedy Python 2's `zip(*iterables)` is like 3's `list(zip(*iterables))` - which is why I suggest importing `izip as zip` for Python 2 in my answer - http://stackoverflow.com/a/36918720/541136 - JF actually assumes Python 2's `zip` in this answer, which compounds the issue of unnecessary creation of long lists in memory. JF's answer uses 200% more memory than necessary for lists (8 bytes per item), as well as the memory for each tuple (relatively enormous at 64 bytes per tuple, see http://stackoverflow.com/a/30316760/541136) 800% more, so 1000% more total (assuming lazy evaluation.) – Russia Must Remove Putin Apr 30 '16 at 00:53
49

I would use a deque with zip to achieve this.

>>> from collections import deque
>>>
>>> l = [1,2,3]
>>> d = deque(l)
>>> d.rotate(-1)
>>> zip(l, d)
[(1, 2), (2, 3), (3, 1)]
villapx
  • 1,743
  • 1
  • 15
  • 31
g.d.d.c
  • 46,865
  • 9
  • 101
  • 111
47

I'd use a slight modification to the pairwise recipe from the itertools documentation:

def pairwise_circle(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ... (s<last>,s0)"
    a, b = itertools.tee(iterable)
    first_value = next(b, None)
    return itertools.zip_longest(a, b,fillvalue=first_value)

This will simply keep a reference to the first value and when the second iterator is exhausted, zip_longest will fill the last place with the first value.

(Also note that it works with iterators like generators as well as iterables like lists/tuples.)

Note that @Barry's solution is very similar to this but a bit easier to understand in my opinion and easier to extend beyond one element.

Tadhg McDonald-Jensen
  • 20,699
  • 5
  • 35
  • 59
39

I would pair itertools.cycle with zip:

import itertools

def circular_pairwise(l):
    second = itertools.cycle(l)
    next(second)
    return zip(l, second)

cycle returns an iterable that yields the values of its argument in order, looping from the last value to the first.

We skip the first value, so it starts at position 1 (rather than 0).

Next, we zip it with the original, unmutated list. zip is good, because it stops when any of its argument iterables are exhausted.

Doing it this way avoids the creation of any intermediate lists: cycle holds a reference to the original, but doesn't copy it. zip operates in the same way.

It's important to note that this will break if the input is an iterator, such as a file, (or a map or zip in ), as advancing in one place (through next(second)) will automatically advance the iterator in all the others. This is easily solved using itertools.tee, which produces two independently operating iterators over the original iterable:

def circular_pairwise(it):
    first, snd = itertools.tee(it)
    second = itertools.cycle(snd)
    next(second)
    return zip(first, second)

tee can use large amounts of additional storage, for example, if one of the returned iterators is used up before the other is touched, but as we only ever have one step difference, the additional storage is minimal.

RoadieRich
  • 6,330
  • 3
  • 35
  • 52
  • 1
    @PythonNut Thanks, that's what I love about `itertools`, and functional programming in general, it allows extremely clean, concise code, while still being easy to read. – RoadieRich Apr 30 '16 at 00:01
  • It fails if the input is an iterator. – jfs May 04 '16 at 12:30
  • 3
    [The `cycle` docs](https://docs.python.org/2/library/itertools.html#itertools.cycle) specifically state that they save a copy of each item in the input iterable -- so you're not saving any space over making a copy of the list. Several of the other answers avoid this problem, using `chain` or `fillvalue`. – agf Jun 05 '16 at 01:01
28

There are more efficient ways (that don't built temporary lists), but I think this is the most concise:

> l = [1,2,3]
> zip(l, (l+l)[1:])
[(1, 2), (2, 3), (3, 1)]
chepner
  • 497,756
  • 71
  • 530
  • 681
  • 7
    Thish as a problem if the list is really long. It creates another list `(l+l)` twice the size. – nigel222 Apr 28 '16 at 16:17
  • Sure; I was going for maximum conciseness in this answer. – chepner Apr 28 '16 at 16:24
  • 3
    "This is a problem if the list is really long." Creating another list, long or not, is not a problem in itself; it's just a fact to be aware of. – brian_o Apr 28 '16 at 18:56
22

Pairwise circular Python 'for' loop

If you like the accepted answer,

zip(L, L[1:] + L[:1])

you can go much more memory light with semantically the same code using itertools:

from itertools import islice, chain #, izip as zip # uncomment if Python 2

And this barely materializes anything in memory beyond the original list (assuming the list is relatively large):

zip(l, chain(islice(l, 1, None), islice(l, None, 1)))

To use, just consume (for example, with a list):

>>> list(zip(l, chain(islice(l, 1, None), islice(l, None, 1))))
[(1, 2), (2, 3), (3, 1)]

This can be made extensible to any width:

def cyclical_window(l, width=2):
    return zip(*[chain(islice(l, i, None), islice(l, None, i)) for i in range(width)])

and usage:

>>> l = [1, 2, 3, 4, 5]
>>> cyclical_window(l)
<itertools.izip object at 0x112E7D28>
>>> list(cyclical_window(l))
[(1, 2), (2, 3), (3, 4), (4, 5), (5, 1)]
>>> list(cyclical_window(l, 4))
[(1, 2, 3, 4), (2, 3, 4, 5), (3, 4, 5, 1), (4, 5, 1, 2), (5, 1, 2, 3)]

Unlimited generation with itertools.tee with cycle

You can also use tee to avoid making a redundant cycle object:

from itertools import cycle, tee
ic1, ic2 = tee(cycle(l))
next(ic2)    # must still queue up the next item

and now:

>>> [(next(ic1), next(ic2)) for _ in range(10)]
[(1, 2), (2, 3), (3, 1), (1, 2), (2, 3), (3, 1), (1, 2), (2, 3), (3, 1), (1, 2)]

This is incredibly efficient, an expected usage of iter with next, and elegant usage of cycle, tee, and zip.

Don't pass cycle directly to list unless you have saved your work and have time for your computer to creep to a halt as you max out its memory - if you're lucky, after a while your OS will kill the process before it crashes your computer.

Pure Python Builtin Functions

Finally, no standard lib imports, but this only works for up to the length of original list (IndexError otherwise.)

>>> [(l[i], l[i - len(l) + 1]) for i in range(len(l))]
[(1, 2), (2, 3), (3, 1)]

You can continue this with modulo:

>>> len_l = len(l)
>>> [(l[i % len_l], l[(i + 1) % len_l]) for i in range(10)]
[(1, 2), (2, 3), (3, 1), (1, 2), (2, 3), (3, 1), (1, 2), (2, 3), (3, 1), (1, 2)]
Russia Must Remove Putin
  • 374,368
  • 89
  • 403
  • 331
21

I would use a list comprehension, and take advantage of the fact that l[-1] is the last element.

>>> l = [1,2,3]
>>> [(l[i-1],l[i]) for i in range(len(l))]
[(3, 1), (1, 2), (2, 3)]

You don't need a temporary list that way.

heemayl
  • 39,294
  • 7
  • 70
  • 76
Atn
  • 521
  • 4
  • 14
  • 2
    You would need `xrange` in Python 2 to avoid a temporary list. Creating a list of number from 1 to n is just as expensive as creating a shallow copy of a list with `n` items. – chepner Apr 28 '16 at 17:56
  • 5
    I think too similar to your to be its own answer, this could also be something like `[(l[i-1], it) for i, it in enumerate(l)]`. You should probably note that both of these methods put the last element requested first. – porglezomp Apr 29 '16 at 03:21
  • 6
    @chepner : I tend to assume python3 when not specified ;) – Atn Apr 29 '16 at 07:35
  • 1
    porglezomp's right, the first element of the list should be (1,2) not (3,1). – Russia Must Remove Putin Apr 30 '16 at 16:42
20

Amazing how many different ways there are to solve this problem.

Here's one more. You can use the pairwise recipe but instead of zipping with b, chain it with the first element that you already popped off. Don't need to cycle when we just need a single extra value:

from itertools import chain, izip, tee

def pairwise_circle(iterable):
    a, b = tee(iterable)
    first = next(b, None)
    return izip(a, chain(b, (first,)))
Barry
  • 286,269
  • 29
  • 621
  • 977
11

I like a solution that does not modify the original list and does not copy the list to temporary storage:

def circular(a_list):
    for index in range(len(a_list) - 1):
        yield a_list[index], a_list[index + 1]
    yield a_list[-1], a_list[0]

for x in circular([1, 2, 3]):
    print x

Output:

(1, 2)
(2, 3)
(3, 1)

I can imagine this being used on some very large in-memory data.

8

This one will work even if the list l has consumed most of the system's memory. (If something guarantees this case to be impossible, then zip as posted by chepner is fine)

l.append( l[0] )
for i in range( len(l)-1):
   pair = l[i],l[i+1]
   # stuff involving pair
del l[-1] 

or more generalizably (works for any offset n i.e. l[ (i+n)%len(l) ] )

for i in range( len(l)):
   pair = l[i], l[ (i+1)%len(l) ]
   # stuff

provided you are on a system with decently fast modulo division (i.e. not some pea-brained embedded system).

There seems to be a often-held belief that indexing a list with an integer subscript is un-pythonic and best avoided. Why?

nigel222
  • 7,582
  • 1
  • 14
  • 22
  • 2
    You last sentence should be a separate question. The answer is that new Python programmers coming from C, C++, or Java write `for i in range(len(a_list)): print a_list[i]` instead of `for x in a_list: print x`. –  Apr 28 '16 at 20:13
  • Indexing a list with integer indexes is unpythonic because if you just need the element, then `for el in iterable:` is the way to go. If you also need the index, `enumerate()` is the way to go. So manual indexing should be avoided. – rubik Apr 29 '16 at 18:56
7

This is my solution, and it looks Pythonic enough to me:

l = [1,2,3]

for n,v in enumerate(l):
    try:
        print(v,l[n+1])
    except IndexError:
        print(v,l[0])

prints:

1 2
2 3
3 1

The generator function version:

def f(iterable):
    for n,v in enumerate(iterable):
        try:
            yield(v,iterable[n+1])
        except IndexError:
            yield(v,iterable[0])

>>> list(f([1,2,3]))
[(1, 2), (2, 3), (3, 1)]
Tonechas
  • 13,398
  • 16
  • 46
  • 80
alec_djinn
  • 10,104
  • 8
  • 46
  • 71
5

How about this?

li = li+[li[0]]
pairwise = [(li[i],li[i+1]) for i in range(len(li)-1)]
Aswin P J
  • 546
  • 4
  • 13
5
from itertools import izip, chain, islice

itr = izip(l, chain(islice(l, 1, None), islice(l, 1)))

(As above with @j-f-sebastian's "zip" answer, but using itertools.)

NB: EDITED given helpful nudge from @200_success. previously was:

itr = izip(l, chain(l[1:], l[:1]))
Community
  • 1
  • 1
shaunc
  • 5,317
  • 4
  • 43
  • 58
  • 1
    If you're going to use `itertools`, then @RoadieRich's solution is better, because it avoids the copying and slicing. – 200_success Apr 29 '16 at 17:01
  • Good point -- for some reason had it in my head that slice was "copy on write". Edited to use islice. – shaunc May 04 '16 at 04:39
3

If you don't want to consume too much memory, you can try my solution:

[(l[i], l[(i+1) % len(l)]) for i, v in enumerate(l)]

It's a little slower, but consume less memory.

satoru
  • 31,822
  • 31
  • 91
  • 141
3

Starting in Python 3.10, the new pairwise function provides a way to create sliding pairs of consecutive elements:

from itertools import pairwise

# l = [1, 2, 3]
list(pairwise(l + l[:1]))
# [(1, 2), (2, 3), (3, 1)]

or simply pairwise(l + l[:1]) if you don't need the result as a list.


Note that we pairwise on the list appended with its head (l + l[:1]) so that rolling pairs are circular (i.e. so that we also include the (3, 1) pair):

list(pairwise(l)) # [(1, 2), (2, 3)]
l + l[:1] # [1, 2, 3, 1]
Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
2

Just another try

>>> L = [1,2,3]
>>> zip(L,L[1:]) + [(L[-1],L[0])]
[(1, 2), (2, 3), (3, 1)]
Eric Tsui
  • 1,924
  • 12
  • 21
1

L = [1, 2, 3] a = zip(L, L[1:]+L[:1]) for i in a: b = list(i) print b

-1

this seems like combinations would do the job.

from itertools import combinations
x=combinations([1,2,3],2)

this would yield a generator. this can then be iterated over as such

for i in x:
  print i

the results would look something like

(1, 2)
(1, 3)
(2, 3)
  • Your results do not match the OP's example. –  May 17 '16 at 18:22
  • really? you are assuming the order matters...I assumed only the combinations mattered. which would mean that this answer is correct. – Hevon Gordon May 20 '16 at 03:59
  • 1
    consider a longer list perhaps, given `[1,2,3,4,5]` the result would be `[(1,2), (2,3), (3,4), (4,5), (5,1)]` which is not combinations but just pairs in the list. – Tadhg McDonald-Jensen Jun 09 '16 at 20:16