37

The problem is easy, I want to iterate over each element of the list and the next one in pairs (wrapping the last one with the first).

I've thought about two unpythonic ways of doing it:

def pairs(lst):
    n = len(lst)
    for i in range(n):
        yield lst[i],lst[(i+1)%n]

and:

def pairs(lst):
    return zip(lst,lst[1:]+lst[:1])

expected output:

>>> for i in pairs(range(10)):
    print i

(0, 1)
(1, 2)
(2, 3)
(3, 4)
(4, 5)
(5, 6)
(6, 7)
(7, 8)
(8, 9)
(9, 0)
>>> 

any suggestions about a more pythonic way of doing this? maybe there is a predefined function out there I haven't heard about?

also a more general n-fold (with triplets, quartets, etc. instead of pairs) version could be interesting.

fortran
  • 74,053
  • 25
  • 135
  • 175

13 Answers13

31
def pairs(lst):
    i = iter(lst)
    first = prev = item = i.next()
    for item in i:
        yield prev, item
        prev = item
    yield item, first

Works on any non-empty sequence, no indexing required.

Katriel
  • 120,462
  • 19
  • 136
  • 170
Martin v. Löwis
  • 124,830
  • 17
  • 198
  • 235
  • 1
    I like the stream quality to it. And the use of iter() is polished. – hughdbrown Aug 10 '09 at 22:08
  • 7
    @Darius, if you need to support empty seq's, use `next(i, sentinel)` in lieu of the old-fashioned `i.next()`, after setting `sentinel = object()`; and, `if first is sentinel: return` just before the `for`. – Alex Martelli Aug 11 '09 at 01:51
  • @Darius: I have clarified that the sequence must be non-empty. It's debatable what it *should* do for an empty sequence: version 1 of the OP yields an empty sequence, and version two raises IndexError. – Martin v. Löwis Aug 11 '09 at 03:35
  • 2
    Crashes on a 1-element sequence: after calling `i.next()` the sequence is empty, so the loop doesn't bind `item`, so `yield item, first` raises an `UnboundLocalError`. – Katriel Feb 19 '12 at 23:10
  • 1
    For **python 3**: replace `i.next()` with `next(i)` and you're all set. And it actually also works on python 2. – j-i-l Oct 23 '18 at 11:53
9

I've coded myself the tuple general versions, I like the first one for it's ellegant simplicity, the more I look at it, the more Pythonic it feels to me... after all, what is more Pythonic than a one liner with zip, asterisk argument expansion, list comprehensions, list slicing, list concatenation and "range"?

def ntuples(lst, n):
    return zip(*[lst[i:]+lst[:i] for i in range(n)])

The itertools version should be efficient enough even for large lists...

from itertools import *
def ntuples(lst, n):
    return izip(*[chain(islice(lst,i,None), islice(lst,None,i)) for i in range(n)])

And a version for non-indexable sequences:

from itertools import *
def ntuples(seq, n):
    iseq = iter(seq)
    curr = head = tuple(islice(iseq, n))
    for x in chain(iseq, head):
        yield curr
        curr = curr[1:] + (x,)

Anyway, thanks everybody for your suggestions! :-)

fortran
  • 74,053
  • 25
  • 135
  • 175
6

I, as always, like tee:

from itertools import tee, izip, chain

def pairs(iterable):
    a, b = tee(iterable)
    return izip(a, chain(b, [next(b)]))
pillmuncher
  • 10,094
  • 2
  • 35
  • 33
5

This might be satisfactory:

def pairs(lst):
    for i in range(1, len(lst)):
        yield lst[i-1], lst[i]
    yield lst[-1], lst[0]

>>> a = list(range(5))
>>> for a1, a2 in pairs(a):
...     print a1, a2
...
0 1
1 2
2 3
3 4
4 0

If you like this kind of stuff, look at python articles on wordaligned.org. The author has a special love of generators in python.

hughdbrown
  • 47,733
  • 20
  • 85
  • 108
2

I'd do it like this (mostly because I can read this):

class Pairs(object):
    def __init__(self, start):
        self.i = start
    def next(self):
        p, p1 = self.i, self.i + 1
        self.i = p1
        return p, p1
    def __iter__(self):
        return self

if __name__ == "__main__":
    x = Pairs(0)
    y = 1
    while y < 20:
        print x.next()
        y += 1

gives:

(0, 1)
(1, 2)
(2, 3)
(3, 4)
(4, 5)
(5, 6)
(6, 7)
(7, 8)
(8, 9)
DrBloodmoney
  • 2,786
  • 2
  • 18
  • 17
1
[(i,(i+1)%len(range(10))) for i in range(10)]

replace range(10) with the list you want.

In general "circular indexing" is quite easy in python; just use:

a[i%len(a)] 
0

To answer your question about solving for the general case:

import itertools

def pair(series, n):
    s = list(itertools.tee(series, n))
    try:
        [ s[i].next() for i in range(1, n) for j in range(i)]
    except StopIteration:
        pass
    while True:
        result = []
        try:
            for j, ss in enumerate(s):
                result.append(ss.next())
        except StopIteration:
            if j == 0:
                break
            else:
                s[j] = iter(series)
                for ss in s[j:]:
                    result.append(ss.next())
        yield result

The output is like this:

>>> for a in pair(range(10), 2):
...     print a
...
[0, 1]
[1, 2]
[2, 3]
[3, 4]
[4, 5]
[5, 6]
[6, 7]
[7, 8]
[8, 9]
[9, 0]
>>> for a in pair(range(10), 3):
...     print a
...
[0, 1, 2]
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]
[4, 5, 6]
[5, 6, 7]
[6, 7, 8]
[7, 8, 9]
[8, 9, 0]
[9, 0, 1]
hughdbrown
  • 47,733
  • 20
  • 85
  • 108
0

This infinitely cycles, for good or ill, but is algorithmically very clear.

from itertools import tee, cycle

def nextn(iterable,n=2):
    ''' generator that yields a tuple of the next n items in iterable.
    This generator cycles infinitely '''
    cycled = cycle(iterable)
    gens = tee(cycled,n)

    # advance the iterators, this is O(n^2)
    for (ii,g) in zip(xrange(n),gens):
        for jj in xrange(ii):
            gens[ii].next()

    while True:
        yield tuple([x.next() for x in gens])


def test():
    data = ((range(10),2),
        (range(5),3),
        (list("abcdef"),4),)
    for (iterable, n) in data:
        gen = nextn(iterable,n)
        for j in range(len(iterable)+n):
            print gen.next()            


test()

gives:

(0, 1)
(1, 2)
(2, 3)
(3, 4)
(4, 5)
(5, 6)
(6, 7)
(7, 8)
(8, 9)
(9, 0)
(0, 1)
(1, 2)
(0, 1, 2)
(1, 2, 3)
(2, 3, 4)
(3, 4, 0)
(4, 0, 1)
(0, 1, 2)
(1, 2, 3)
(2, 3, 4)
('a', 'b', 'c', 'd')
('b', 'c', 'd', 'e')
('c', 'd', 'e', 'f')
('d', 'e', 'f', 'a')
('e', 'f', 'a', 'b')
('f', 'a', 'b', 'c')
('a', 'b', 'c', 'd')
('b', 'c', 'd', 'e')
('c', 'd', 'e', 'f')
('d', 'e', 'f', 'a')
Gregg Lind
  • 20,690
  • 15
  • 67
  • 81
  • I like the use of itertools.cycle(). I am not sure why your test code does range(len(iter) + n). I think range(len(iter)) is correct. Your n\*\*2 code to advance the iterators is like my code: [ s[i].next() for i in range(1, n) for j in range(i)]. – hughdbrown Aug 11 '09 at 00:55
  • re: len(iter)+n... I wanted to show that it really does cycle infinitely. it's just in the test code, after all. Your one-liner for advancing is shorter/better. In any case, that's the one line in the code that definitely needs a comment, since it's the crux of the algorithm :) – Gregg Lind Aug 11 '09 at 01:00
0
def pairs(ex_list):
    for i, v in enumerate(ex_list):
        if i < len(list) - 1:
            print v, ex_list[i+1]
        else:
            print v, ex_list[0]

Enumerate returns a tuple with the index number and the value. I print the value and the following element of the list ex_list[i+1]. The if i < len(list) - 1 means if v is not the last member of the list. If it is: print v and the first element of the list print v, ex_list[0].

Edit:

You can make it return a list. Just append the printed tuples to a list and return it.

def pairs(ex_list):
    result = []
    for i, v in enumerate(ex_list):
        if i < len(list) - 1:
            result.append((v, ex_list[i+1]))
        else:
            result.append((v, ex_list[0]))
    return result
Community
  • 1
  • 1
alexpinho98
  • 909
  • 8
  • 14
0

Even shorter version of Fortran's zip * range solution (with lambda this time;):

group = lambda t, n: zip(*[t[i::n] for i in range(n)])

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

gives:

[(1, 2), (3, 4)]
MKTech
  • 21
  • 2
  • nice, but not quite right, what I needed was to iterate in pairs, but with repetition of the elements and wrapping, for your example the output should be `[(1,2),(2,3),(3,4),(4,1)]` – fortran Feb 10 '10 at 08:12
0

Of course, you can always use a deque:

from collections import deque
from itertools import *

def pairs(lst, n=2):
    itlst = iter(lst)
    start = list(islice(itlst, 0, n-1))
    deq = deque(start, n)
    for elt in chain(itlst, start):
        deq.append(elt)
        yield list(deq)
user3552819
  • 116
  • 2
0

Here's a version that supports an optional start index (for example to return (4, 0) as the first pair, use start = -1:

import itertools

def iterrot(lst, start = 0):

    if start == 0:
        i = iter(lst)
    elif start > 0:
        i1 = itertools.islice(lst, start, None)
        i2 = itertools.islice(lst, None, start)
        i = itertools.chain(i1, i2)
    else:
        # islice doesn't support negative slice indices so...
        lenl = len(lst)
        i1 = itertools.islice(lst, lenl + start, None)
        i2 = itertools.islice(lst, None, lenl + start)
        i = itertools.chain(i1, i2)
    return i


def iterpairs(lst, start = 0):

    i = iterrot(lst, start)     

    first = prev = i.next()
    for item in i:
        yield prev, item
        prev = item
    yield prev, first


def itertrios(lst, start = 0):

    i = iterrot(lst, start)     

    first = prevprev = i.next()
    second = prev = i.next()
    for item in i:
        yield prevprev, prev, item
        prevprev, prev = prev, item

    yield prevprev, prev, first
    yield prev, first, second
-1
i=(range(10))

for x in len(i):
    print i[:2]
    i=i[1:]+[i[1]]

more pythonic than this is impossible