views:

1520

answers:

7

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[0]])

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.

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

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

Martin v. Löwis
I like the stream quality to it. And the use of iter() is polished.
hughdbrown
Doesn't work on an empty sequence.
Darius Bacon
@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
@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
+3  A: 

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
Yeah, I noticed that and I've fixed it -- in the space of time it took to down-vote it. My fault, really.
hughdbrown
A: 

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
well, pair() is misnamed now ;)
Stefano Borini
Yes, misnamed, not pretty, and 2 minutes after the OP's pythoned-to-the-max self-answer. Ah well.
hughdbrown
+2  A: 

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)])

Anyway, thanks everybody for your suggestions! :-)

fortran
Your first answer (in your question) is a million times easier to understand than either of these. That makes it much more pythonic in my book :-/
John Fouhy
hmmmm... maybe it's been too much codegolf for me ^-^
fortran
try this: "for a in ntuples(count(), 3): print a;" I think you need to use itertools.tee() to get this to work.
hughdbrown
count is not an indexable sequence, so it's normal it doesn't work with this method that uses slices extensively...
fortran
Hoho, got to love the sarcasm!
Beau Martínez
+2  A: 

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
+1 - Personally, I like this solution much better than the 'yield' based solutions.
Tall Jeff
A: 

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
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
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
A: 

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)]
Wiktor Kolodziej
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