The built-in range
gives us ascending and descending sequences:
>>> list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> list(range(9, -1, -1))
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
Notice, each range
already "represents" the sequence, and we can iterate over it in a for
loop for example. Using list
here is just to show all the values.
If we just want to iterate over both of those ranges, we could just write two loops. However, if those loops are doing the same thing, maybe we instead want to join them together into the same sequence. For this, we use chain
from the standard library itertools
module:
>>> import itertools
>>> list(itertools.chain(range(10), range(9, -1, -1)))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
The 9
was repeated, because it's in both ranges; we can easily adjust either of the ranges to avoid that. For example:
>>> import itertools
>>> list(itertools.chain(range(9), range(9, -1, -1)))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
What if we want to repeat the sequence forever (or until some break
condition is met)? We could just wrap our for
loop over that sequence, inside a while
loop; but it would be neater if we could make the sequence itself be unbounded, and repeat the pattern indefinitely when we iterate.
We can also do that with the standard library, using cycle
, also from itertools
. We will want to remove the 0
from the end to avoid duplicating it when the pattern starts over; fortunately, that's just as easy as before - just set the end point for the second range appropriately. We get something quite elegant. I will wrap up the sequence creation in a function, too:
from itertools import cycle, chain
def up_and_down(n):
return cycle(chain(range(n), range(n, 0, -1)))
Let's test it:
>>> import time
>>> for i in up_and_down(180):
... print(f'{i: 4}', end='\r')
... time.sleep(0.1)
We can see (until we abort with ctrl-C) the counter smoothly count up to 180 and back down to 0, then repeat (if we wait 36 seconds).