9

I can't believe this is nowhere to be found, but: I want all consecutive pairs from an array, including the last element with the first one. I tried:

[(a, b) for a, b in zip(list, list[1:])]

What is the most pythonic and efficient way to do it?

martineau
  • 119,623
  • 25
  • 170
  • 301
John
  • 465
  • 1
  • 6
  • 13

4 Answers4

12

Another approach would be to use modulo to jump back from the last element to the first one :

l = [1,2,3,4,5,6]
n = len(l)
[(l[i], l[(i+1) % n]) for i in range(n)]

It returns :

[(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 1)]
Eric Duminil
  • 52,989
  • 9
  • 71
  • 124
10

you just have to add the first element to the second list:

l = [1,2,3,4,5,6]

r = [(a, b) for a, b in zip(l, l[1:]+l[:1])]

result:

[(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 1)]

Aside: "pythonic" also means not using list as a variable name.

Variant: use itertools.ziplongest instead of zip and fill with first element (as a bonus, also works with numpy arrays since no addition):

import itertools
r = [(a, b) for a, b in itertools.zip_longest(l, l[1:], fillvalue=l[0])]
Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
7

Here's another approach using a collections.deque:

>>> from collections import deque
>>> x = list(range(7))
>>> d = deque(x)
>>> d.rotate(-1)
>>> list(zip(x,d))
[(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 0)]
>>>
Moinuddin Quadri
  • 46,825
  • 13
  • 96
  • 126
juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172
5

I'm not disputing the coolness of some of these answers but nobody seems to have timed them and, in terms of being "Pythonic", they can get a little obscure compared to just appending or extending the circular part to the end.

l = xrange(1, 10000)

def simple_way(list_input):
    a = [(list_input[i], list_input[i+1]) for i in xrange(len(list_input)-1)]
    a.append((list_input[-1], list_input[0]))
    return a


timeit simple_way(l)
1000 loops, best of 3: 1.14 ms per loop

def top_rated_answer(list_input):
    n = len(list_input)
    a = [(list_input[i], list_input[(i+1) % n]) for i in xrange(n)]
    return a


timeit top_rated_answer(l)
1000 loops, best of 3: 1.37 ms per loop
roganjosh
  • 12,594
  • 4
  • 29
  • 46
  • 1
    try `a.append((l[-1], l[0]))` for a potentially even faster answer. – Jean-François Fabre Feb 14 '17 at 06:54
  • @Jean-FrançoisFabre it's giving me the same speed either way – roganjosh Feb 14 '17 at 08:26
  • ok, but it avoids the creation of a temp list. Less characters & same speed => better :) – Jean-François Fabre Feb 14 '17 at 08:27
  • @Jean-FrançoisFabre that's true, I will update. I just wanted to demo that no fancy tricks were needed to make this fast (although it does take up a second line though :) ). – roganjosh Feb 14 '17 at 08:30
  • 1
    one-liners are sexy but sometimes a good old C-like coding is faster, yes. Even, modulo is costly. – Jean-François Fabre Feb 14 '17 at 08:35
  • 1
    Deserves more upvotes due to analysis if nothing else. Want the best way to do something? test! – Baldrickk Feb 14 '17 at 09:08
  • @Baldrickk indeed. I realised I had a couple of global references in the tests as I packaged them into functions for testing. I've updated the answer and the approach by Eric has improved more than my simple approach, but it's still appreciably faster to go with the approach that's easier to read. – roganjosh Feb 14 '17 at 09:20