Apalala - 4 months ago 21

Python Question

Often enough, I've found the need to process a list by pairs. I was wondering which would be the pythonic and efficient way to do it, and found this on Google:

`pairs = zip(t[::2], t[1::2])`

I thought that was pythonic enough, but after a recent discussion involving idioms versus efficiency, I decided to do some tests:

`import time`

from itertools import islice, izip

def pairs_1(t):

return zip(t[::2], t[1::2])

def pairs_2(t):

return izip(t[::2], t[1::2])

def pairs_3(t):

return izip(islice(t,None,None,2), islice(t,1,None,2))

A = range(10000)

B = xrange(len(A))

def pairs_4(t):

# ignore value of t!

t = B

return izip(islice(t,None,None,2), islice(t,1,None,2))

for f in pairs_1, pairs_2, pairs_3, pairs_4:

# time the pairing

s = time.time()

for i in range(1000):

p = f(A)

t1 = time.time() - s

# time using the pairs

s = time.time()

for i in range(1000):

p = f(A)

for a, b in p:

pass

t2 = time.time() - s

print t1, t2, t2-t1

These were the results on my computer:

`1.48668909073 2.63187503815 1.14518594742`

0.105381965637 1.35109519958 1.24571323395

0.00257992744446 1.46182489395 1.45924496651

0.00251388549805 1.70076990128 1.69825601578

If I'm interpreting them correctly, that should mean that the implementation of lists, list indexing, and list slicing in Python is very efficient. It's a result both comforting and unexpected.

Note that if the list has an odd number of elements then the last one will not be in any of the pairs.

I added these two suggestions from the answers to the tests:

`def pairwise(t):`

it = iter(t)

return izip(it, it)

def chunkwise(t, size=2):

it = iter(t)

return izip(*[it]*size)

These are the results:

`0.00159502029419 1.25745987892 1.25586485863`

0.00222492218018 1.23795199394 1.23572707176

Most pythonic and very efficient:

`pairs = izip(t[::2], t[1::2])`

Most efficient and very pythonic:

`pairs = izip(*[iter(t)]*2)`

It took me a moment to grok that the first answer uses two iterators while the second uses a single one.

To deal with sequences with an odd number of elements, the suggestion has been to augment the original sequence adding one element (

`None`

`itertools.izip_longest()`

Note that, in Python 3.x,

`zip()`

`itertools.izip()`

`itertools.izip()`

Answer

My favorite way to do it:

```
from itertools import izip
def pairwise(t):
it = iter(t)
return izip(it,it)
# for "pairs" of any length
def chunkwise(t, size=2):
it = iter(t)
return izip(*[it]*size)
```

When you want to pair all elements you obviously might need a fillvalue:

```
from itertools import izip_longest
def blockwise(t, size=2, fillvalue=None):
it = iter(t)
return izip_longest(*[it]*size, fillvalue=fillvalue)
```