Iterate over pairs in a list (circular fashion) in Python

Question:

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.

Asked By: fortran

||

Answers:

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.

Answered By: Martin v. Löwis

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.

Answered By: hughdbrown

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]
Answered By: hughdbrown

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! 🙂

Answered By: fortran

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)
Answered By: DrBloodmoney

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')
Answered By: Gregg Lind

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)]
Answered By: MKTech
i=(range(10))

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

more pythonic than this is impossible

Answered By: Malkyus

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
Answered By: Jay Billfinger
[(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)] 

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)]))
Answered By: pillmuncher
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
Answered By: alexpinho98

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)
Answered By: user3552819
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.