Image

The Python Coding Stack: The Chores Rota (#3 in The `itertools` Series • `cycle()` and Combining Too

"It's your turn to take the bins out."

"No way, I washed up the dishes today, and vacuumed the rugs yesterday."

"But…"

And on and on it went. Yteria and her flatmate, Silvia, had these arguments every day. Yteria was hoping she'd be able to move soon—move to a new area and to a flat she didn't have to share with Silvia…or anyone else.

"Right. Let me set up a rota and then we'll follow it strictly", and Yteria got straight to work.

It had been several weeks since Yteria had lost the word "for". For this world is a bit different to yours and mine. People can lose words through some mishap or nefarious means. And if you lose a word, you can't speak it, you can't write it, you can't type it. You still know it's there somewhere, that it exists in the language, but you can't use it.

You can follow Yteria's origin story, how she lost the word "for" and the challenges she faced when programming here:The ‘itertools’ Series.

It’s unlikely you care, but I’ll tell you anyway. I launched a new publication last week. But it’s completely unrelated to Python and it’s unlikely there will be much overlap between the two audiences. Still, if you want to follow my ‘back to the future’ journey, here’s the first post that introduces the publication:Back on the Track • The 25-Year Gap • #1

Creating Infinite Iterables from Finite Ones

Yteria set up two lists, one with the chores and another with Silvia's name and her own:

Next, she wanted to write code to convert these lists into infinite sequences by repeating the contents of the lists forever:

But then she stopped.

Yteria had been programming without the ability to use the word "for" for several weeks by now. And she had discovered theitertoolsmodule in Python's standard library. This module came to her rescue on several occasions.

And there it was:itertools.cycle(). It was the perfect tool for what she needed:

The functionitertools.cycle()accepts any iterable and returns an iterator that will keep yielding items from the original iterable, restarting from the beginning each time it reaches the end.

If you want to brush up on the difference betweeniterableanditerator, you can read the following articles:

Iterable: Python's Stepping Stones • (Data Structure Categories #1)

A One-Way Stream of Data • Iterators in Python (Data Structure Categories #6)

But before we move on, let's still write thecreate_infinite_sequence()function Yteria was about to write. A version of this function could be as follows:

This function includes ayieldrather than areturn. Therefore, this is a generator function. Calling this function creates a generator. You can read more about generators in this article:Pay As You Go • Generate Data Using Generators (Data Structure Categories #7)

A generator created from this generator function starts withindexequal to0and, therefore, starts by yielding the first element in the sequence. Next time, it yields the second, and so on. However, the final line in the function definition uses aconditional expressionto reset the index to zero whenever it reaches the end of the sequence.

So, for a list with three elements, such astasks, here are the first few steps:

The generator starts withindexequal to0, yields the first element, then incrementsindexto1. The increment happens in the conditional expression. Note how the third operand in the conditional expression—the one after theelse—isindex + 1.

Sinceindexis now1, the generator yields the second element and incrementsindexto2.

When the generator yieldssequence[2], the conditional expression resetsindexto0sinceindex, which is2, is equal tolen(sequence) - 1.

The generator then yields the first element of the sequence and the whole process repeats itself.

Let's confirm that this gives the same output asitertools.cycle():

So, does it matter which option you choose?

Yes, it does.

First of all, once you know aboutitertools.cycle(), it's much easier and quicker to use it than to write your own function. It also makes your code more readable for anyone who's aware ofitertools.cycle()—and even if they're not, the function name gives a good clue to what it does.

A second advantage of usingitertools.cycle()is that it works with any iterable. Thecreate_infinite_sequence()generator function only works with sequences. A sequence is an ordered collection in which you can use integers as indices to fetch data based on the order of the elements in the sequence. You can read more about sequences here:Sequences in Python (Data Structure Categories #2)

In Python, all sequences are iterable, but not all iterables are sequences. For example, dictionaries are iterable but they're not sequences. Therefore,itertools.cycle()can be used on a larger group of data types thancreate_infinite_sequence().

And finally, there's another really good reason to useitertools.cycle()instead of a homemade function:

You create two iterators. The first one,infinite_tasks, is the generator you get from the generator functioncreate_infinite_sequence(). Note that all generators are iterators.

The second iterator isinfinite_tasks_cyc, which is the iterator thatitertools.cycle()returns. All the tools initertoolsreturn iterators.

Finally, you time how long it takes to get the first 10 million elements from each of these infinite iterators. Here's the output I got on my computer—your timings may vary:

Using 'create_infinite_sequence()': 0.753838583000288 Using 'itertools.cycle()': 0.19026683299944125

It's much quicker to useitertools.cycle(). Sure, you may have ideas on writing a more efficient algorithm than the one I used increate_infinite_sequence(). Go ahead, I'm sure you'll be able to do better thancreate_infinite_sequence(). But can you do better thanitertools.cycle()?

Do you want to join a forum to discuss Python further with other Pythonistas? Upgrade to a paid subscription here on The Python Coding Stack to get exclusive access toThe Python Coding Place's members' forum. More Python. More discussions. More fun.

Subscribe now

And you'll also be supporting this publication. I put plenty of time and effort into crafting each article. Your support will help me keep this content coming regularly and, importantly, will help keep it free for everyone.

Creating the Rota • Combining Tools Using 'Iterator Algebra'

So, Yteria useditertools.cycle()to create two infinite iterators: one fortasksand the other forpeople. Note that the original lists,tasksandpeople, don't have the same number of elements.

Next, Yteria needed to find a way to connect these two infinite iterators so that corresponding elements are matched. She needed a way to progress through the two infinite iterators at the same time. She needed something to "glue" them together…

…or better still, to "zip" them together.

This is wherezip()comes in. Thezip()built-in tool takes a number of iterators and zips them together, grouping the first elements of each iterator together, then grouping the second elements of each iterator together, and so on:

And there it is. Remember thatrotais an iterator sincezip()returns an iterator. So, each time you fetch the next value from therotaiterator, you'll get a pairing between a person and the chore they need to do.

Yteria finished this off with some quick code to display each day's rota. It would have been easier to use aforloop, but she couldn't. So she opted for this option, which is less tidy but still works:

You can write the easierforloop version if you prefer. Note how Yteria, who's now proficient with theitertoolsmodule, also useditertools.count()to create a counter! She could have just created an integer and increment it each time, of course.

Side note: Thewhileloop above feels like something that could be implemented with the help of someitertoolstools. Yteria felt this way, too. She wrote a note to try to refactor thiswhileloop later, even if just as an exercise in playing with more of the tools initertools. Do you want to have a go, too? If Yteria gets round to replacing this code, I'll let you know in a future post inThe 'itertools' Series.

Here's the output from this code for the first few days:

Press enter for the next day's rota... Day 1: It's Yteria's turn to take the bins out It's Silvia's turn to clean floor and carpets It's Yteria's turn to wash up 
 Press enter for the next day's rota... Day 2: It's Silvia's turn to take the bins out It's Yteria's turn to clean floor and carpets It's Silvia's turn to wash up 
 Press enter for the next day's rota... Day 3: It's Yteria's turn to take the bins out It's Silvia's turn to clean floor and carpets It's Yteria's turn to wash up 
 Press enter for the next day's rota... Day 4: It's Silvia's turn to take the bins out It's Yteria's turn to clean floor and carpets It's Silvia's turn to wash up 
 Press enter for the next day's rota...

And of course, this code works with any number of tasks and any number of people.

Theitertoolsdocumentation pagehas a great line about combining various iteration tools using 'iterator algebra'. Yteria's solution is an example of this. It combines two iteration tools,zip()andcycle(), to provide a neat solution. The tools initertoolsare often useful as standalone tools. But they're even more powerful when you combine them with each other.

Note thatzip()andenumerate()aren't part ofitertoolssince they're both built-in callables. However, they fall in the same category as the other tools initertools—they're tools to help in particular iteration tasks.

Support The Python Coding Stack

Final Words

Problem solved. Yteria and Silvia could now share the daily chores and make sure that everyone contributes equally. Yteria felt that her forced abstention from using theforkeyword in Python led her to understand Pythonic iteration a lot better. She felt like an iteration pro now! Iterators are at the heart of iteration in Python. And itertools provides lots of useful iterators.

Code in this article uses Python 3.13

The code images used in this article are created usingSnappify.[Affiliate link]

You can also support this publication by making aone-off contribution of any amount you wish.

Support The Python Coding Stack

For more Python resources, you can also visitReal Python—you may even stumble on one of my own articles or courses there!

Also, are you interested in technical writing? You’d like to make your own writing more narrative, more engaging, more memorable? Have a look atBreaking the Rules.

And you can find out more about me atstephengruppetta.com

Further reading related to this article’s topic:

The ‘itertools’ Series

Iterable: Python's Stepping Stones • (Data Structure Categories #1)

Sequences in Python (Data Structure Categories #2)

A One-Way Stream of Data • Iterators in Python (Data Structure Categories #6)

Pay As You Go • Generate Data Using Generators (Data Structure Categories #7)

If You Find if..else in List Comprehensions Confusing, Read This, Else…

Read the other articles in TheitertoolsSeries:

Appendix: Code Blocks

Code Block #1
tasks = ["Take the bins out", "Clean floor and carpets", "Wash up"] people = ["Yteria", "Silvia"]
Code Block #2
def create_infinite_sequence(sequence): ...
Code Block #3
tasks = ["Take the bins out", "Clean floor and carpets", "Wash up"] import itertools tasks_cyc = itertools.cycle(tasks) next(tasks_cyc) # 'Take the bins out' next(tasks_cyc) # 'Clean floor and carpets' next(tasks_cyc) # 'Wash up' next(tasks_cyc) # 'Take the bins out' next(tasks_cyc) # 'Clean floor and carpets' next(tasks_cyc) # 'Wash up'
Code Block #4
def create_infinite_sequence(sequence): index = 0 while True: yield sequence[index] index = 0 if index == len(sequence) - 1 else index + 1
Code Block #5
tasks_inf_seq = create_infinite_sequence(tasks) next(tasks_inf_seq) # 'Take the bins out' next(tasks_inf_seq) # 'Clean floor and carpets' next(tasks_inf_seq) # 'Wash up' next(tasks_inf_seq) # 'Take the bins out' next(tasks_inf_seq) # 'Clean floor and carpets' next(tasks_inf_seq) # 'Wash up'
Code Block #6
import itertools import timeit tasks = ["Take the bins out", "Clean floor and carpets", "Wash up"] people = ["Yteria", "Silvia"] def create_infinite_sequence(sequence): index = 0 while True: yield sequence[index] index = 0 if index == len(sequence) - 1 else index + 1 infinite_tasks = create_infinite_sequence(tasks) infinite_tasks_cyc = itertools.cycle(tasks) print( "Using 'create_infinite_sequence()':\n", timeit.timeit( "next(infinite_tasks)", number=10_000_000, globals=globals(), ) ) print( "Using 'itertools.cycle()':\n", timeit.timeit( "next(infinite_tasks_cyc)", number=10_000_000, globals=globals(), ) )
Code Block #7
import itertools tasks = ["Take the bins out", "Clean floor and carpets", "Wash up"] people = ["Yteria", "Silvia"] rota = zip( itertools.cycle(people), itertools.cycle(tasks), )
Code Block #8
import itertools tasks = ["Take the bins out", "Clean floor and carpets", "Wash up"] people = ["Yteria", "Silvia"] rota = zip( itertools.cycle(people), itertools.cycle(tasks), ) day_counter = itertools.count(start=1) while True: input("\nPress enter for the next day's rota...") day = next(day_counter) print(f"Day {day}:") # The next bit would be easier using a 'for' loop, # but Yteria couldn't do this! while True: person, task = next(rota) print(f"It's {person}'s turn to {task.lower()}") if task == tasks[-1]: break

For more Python resources, you can also visitReal Python—you may even stumble on one of my own articles or courses there!

Also, are you interested in technical writing? You’d like to make your own writing more narrative, more engaging, more memorable? Have a look atBreaking the Rules.

And you can find out more about me atstephengruppetta.com

https://www.thepythoncodingstack.com/p/itertools-cycle-and-iterator-algebra-the-chores-rota-3-in-the-itertools-series