By Matt Luongo


2010-04-13 16:11:51 8 Comments

I feel like I spend a lot of time writing code in Python, but not enough time creating Pythonic code. Recently I ran into a funny little problem that I thought might have an easy, idiomatic solution. Paraphrasing the original, I needed to collect every sequential pair in a list. For example, given the list [1,2,3,4,5,6], I wanted to compute [(1,2),(3,4),(5,6)].

I came up with a quick solution at the time that looked like translated Java. Revisiting the question, the best I could do was

l = [1,2,3,4,5,6]
[(l[2*x],l[2*x+1]) for x in range(len(l)/2)]

which has the side effect of tossing out the last number in the case that the length isn't even.

Is there a more idiomatic approach that I'm missing, or is this the best I'm going to get?

8 comments

@Ignacio Vazquez-Abrams 2010-04-13 16:20:47

The one often-quoted is:

zip(*[iter(l)] * 2)

I prefer this more readable version of the iter solution:

it = iter(l)
list(zip(it, it))
# [(1, 2), (3, 4), (5, 6)]

@Kamil Szot 2010-04-13 16:28:53

awsome, but reads like perl ;-)

@jemfinch 2010-04-13 16:30:37

True, but it's also the most efficient solution presented so far, I think. I'll test, brb.

@jemfinch 2010-04-13 16:32:55

Yes, it's faster than RichieHindle's solution by about 10%, since it doesn't require any allocation of memory and only one iteration over the input list.

@Dana 2010-04-13 16:45:14

+1 because I never knew about * to unpack an argument list before!

@Mike Graham 2010-04-13 17:13:43

This is a slick approach, but obviously less readable, especially to less advanced Python developers. This is why I like using grouper to abstract away what it does.

@Matt Luongo 2010-04-15 22:18:52

Know that, while RichieHindle won the accepted answer (really hard to ignore readability), this answer won my heart.

@Chris Martin 2014-10-31 19:26:32

It's also worth noting that this is straight out of the Python documentation.

@beardc 2015-08-11 17:37:56

toolz is a well-built library with many functional programming niceties overlooked in itertools. partition solves this (with an option to pad the last entry for lists of odd length)

>>> list(toolz.partition(2, [1,2,3,4,5,6]))
[(1, 2), (3, 4), (5, 6)]

@moshez 2010-04-13 23:08:56

The right thing is probably not to compute lists, but to write an iterator->iterator function. This is more generic -- it works on every iterable, and if you want to "freeze" it into a list, you can use the "list()" function.

def groupElements(iterable, n):
    # For your case, you can hardcode n=2, but I wanted the general case here.
    # Also, you do not specify what to do if the 
    # length of the list is not divisible by 2
    # I chose here to drop such elements
    source = iter(iterable)
    while True:
        l = []
        for i in range(n):
            l.append(source.next())
        yield tuple(l)

I'm surprised the itertools module does not already have a function for that -- perhaps a future revision. Until then, feel free to use the version above :)

@LondonRob 2015-08-14 06:52:00

What's this like performance-wise?

@Kamil Szot 2010-04-13 21:08:29

If you don't want to lose elements if their number in list is not even try this:

>>> l = [1, 2, 3, 4, 5]
>>> [(l[i],  l[i+1] if i+1 < len(l) else None)  for i in range(0, len(l), 2)]
[(1, 2), (3, 4), (5, None)]

@prasanna 2010-04-13 16:22:31

try this

def pairs(l, n):
    return zip(*[l[i::n] for i in range(n)])

So,

pairs([1, 2, 3, 4], 2) gives

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

@Mike Graham 2010-04-13 16:23:39

I usually copy the grouper recipe from the itertools documentation into my code for this.

def grouper(n, iterable, fillvalue=None):
    "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return izip_longest(fillvalue=fillvalue, *args)

@Isaac 2010-04-13 16:16:43

How about using the step feature of range():

[(l[n],l[n+1]) for n in range(0,len(l),2)]

@RichieHindle 2010-04-13 16:15:58

This will do it a bit more neatly:

>>> data = [1,2,3,4,5,6]
>>> zip(data[0::2], data[1::2])
[(1, 2), (3, 4), (5, 6)]

(but it's arguably less readable if you're not familiar with the "stride" feature of ranges).

Like your code, it discards the last value where you have an odd number of values.

@badp 2010-04-13 16:22:34

You can do a data += [None] before doing the above to handle the odd number of items scenario.

@Mike Graham 2010-04-13 17:14:10

some_list += [foo] is written some_list.append(foo).

@Johnsyweb 2011-03-22 10:15:33

How many times does this iterate over the data? Take a look at this answer

Related Questions

Sponsored Content

10 Answered Questions

[SOLVED] Does Python have a string 'contains' substring method?

11 Answered Questions

[SOLVED] Getting the last element of a list

  • 2009-05-30 19:28:53
  • Janusz
  • 1832382 View
  • 1861 Score
  • 11 Answer
  • Tags:   python list indexing

7 Answered Questions

[SOLVED] How do I get the number of elements in a list?

  • 2009-11-11 00:30:54
  • y2k
  • 3152262 View
  • 1838 Score
  • 7 Answer
  • Tags:   python list

16 Answered Questions

[SOLVED] What are metaclasses in Python?

29 Answered Questions

[SOLVED] Finding the index of an item given a list containing it in Python

  • 2008-10-07 01:39:38
  • Eugene M
  • 3499432 View
  • 2871 Score
  • 29 Answer
  • Tags:   python list indexing

36 Answered Questions

[SOLVED] How to get the current time in Python

  • 2009-01-06 04:54:23
  • user46646
  • 3054789 View
  • 2602 Score
  • 36 Answer
  • Tags:   python datetime time

62 Answered Questions

[SOLVED] Calling an external command from Python

23 Answered Questions

[SOLVED] Does Python have a ternary conditional operator?

20 Answered Questions

25 Answered Questions

[SOLVED] How can I safely create a nested directory?

Sponsored Content