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.