Pairwise circular Python 'for' loop

I would use a deque with zip to achieve this.

>>> from collections import deque
>>>
>>> l = [1,2,3]
>>> d = deque(l)
>>> d.rotate(-1)
>>> zip(l, d)
[(1, 2), (2, 3), (3, 1)]

A Pythonic way to access a list pairwise is: zip(L, L[1:]). To connect the last item to the first one:

>>> L = [1, 2, 3]
>>> zip(L, L[1:] + L[:1])
[(1, 2), (2, 3), (3, 1)]

I would pair itertools.cycle with zip:

import itertools

def circular_pairwise(l):
    second = itertools.cycle(l)
    next(second)
    return zip(l, second)

cycle returns an iterable that yields the values of its argument in order, looping from the last value to the first.

We skip the first value, so it starts at position 1 (rather than 0).

Next, we zip it with the original, unmutated list. zip is good, because it stops when any of its argument iterables are exhausted.

Doing it this way avoids the creation of any intermediate lists: cycle holds a reference to the original, but doesn't copy it. zip operates in the same way.

It's important to note that this will break if the input is an iterator, such as a file, (or a map or zip in python-3), as advancing in one place (through next(second)) will automatically advance the iterator in all the others. This is easily solved using itertools.tee, which produces two independently operating iterators over the original iterable:

def circular_pairwise(it):
    first, snd = itertools.tee(it)
    second = itertools.cycle(snd)
    next(second)
    return zip(first, second)

tee can use large amounts of additional storage, for example, if one of the returned iterators is used up before the other is touched, but as we only ever have one step difference, the additional storage is minimal.


I'd use a slight modification to the pairwise recipe from the itertools documentation:

def pairwise_circle(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ... (s<last>,s0)"
    a, b = itertools.tee(iterable)
    first_value = next(b, None)
    return itertools.zip_longest(a, b,fillvalue=first_value)

This will simply keep a reference to the first value and when the second iterator is exhausted, zip_longest will fill the last place with the first value.

(Also note that it works with iterators like generators as well as iterables like lists/tuples.)

Note that @Barry's solution is very similar to this but a bit easier to understand in my opinion and easier to extend beyond one element.

Tags:

Python