`cyclic` kwarg for `itertools.pairwise`

I’m creating this discussion to see how the Python community would feel about adding a cyclic boolean kwarg to itertools.pairwise? Conceptually this is very simple, and would be equivalent to

def pairwise(iterable, cyclic=False):
    if not cyclic:
        return itertools.pairwise(iterable)
    a, b = tee(iterable)
    first = next(b, None)
    return zip(a, chain(b, (first,)))

This is a pattern that comes up quite often in graph theory when working with cycle graphs (instead of path graphs which can be represented with the current version of pairwise)—in fact, this proposal came up while discussion the network function networkx.utils.pairwise, but it’d be nice to have a full C implementation of this that would render the nx one unnecessary.

1 Like

Nice. I can definitely see the use cases for this when working with graph cycles, although if I deal with graphs they are usually acyclic, or are trees. Of course, your proposed option would be made somewhat redundant if iterables permitted left- and right-cyclic shifts (cyclic permutations) in the manner of:

>>> it = [1, 2, 3, 4]
>>> it.cycle(left=1)
>>> [2, 3, 4, 1]

where k is the cycle length taken modulo n = len(it), in which case your addition of cyclicto itertools.pairwise boils down to:

>>> it = [1, 2, 3, 4]
>>> list(zip(it, it.cycle(left=1)))
[(1, 2), (2, 3), (3, 4), (4, 1)]

Let’s make iterables more interesting by allowing cyclic permutations!

1 Like

Not surprised that there was a previous request for this kind of feature, as it would be a useful addition for cyclic graphs, but I suppose in the general scheme of feature requests this could appear as rather niche.

In my view the reference to itertools.cycle in the prior discussion isn’t relevant: that returns an iterator that will repeat the elements of the iterable infinitely. It’s a repeater, whereas the kind of result requested by the OP could be achieved with (finite) cyclic permutations/rotations.

I think it’d be a useful option, but am a bit unclear about what the output of the edge case of pairwise([1], cyclic=True) should be. In my mind it should yield nothing because there isn’t any pair of vertices, but your code would yield (1, 1).

2 Likes

For a cyclic (sub)graph it only makes sense to say you’ve cycled back to a node when you’re on a different node, so for the one element/node case it does makes sense to yield nothing for this cyclic option that’s been described. This would also cover the pathological example of a singleton graph where the sole node - call it 1 - has a loop on itself (the sole edge is (1, 1)).

1 Like