# Itertools for CircuitPython

## Overview

![](https://cdn-learn.adafruit.com/assets/assets/000/073/601/medium800/circuitpython_refresh-round-symbol.png?1553520664)

## Iteration

What is iteration? Doing the same thing repeatedly, generally with a different, but related, piece of data. Programmers use iteration all the time, often even the ones who are using [recursion](https://learn.adafruit.com/scheme-in-circuitpython/lisp).

Every time you are using `for` to process items in a list, you're using iteration. Every time you use a list comprehension, you're using iteration.

It's so common that Python has some builtin constructs to make it easier: iterators, iterables, and generators. These were covered in [another guide](https://learn.adafruit.com/circuitpython-101-list-and-things-iterators-generators/).&nbsp; As glimpsed in that guide, Python has a module that provides a wealth of tools for working with these: **itertools**.

In that guide we briefly looked at&nbsp;the small subset of itertools that has been [ported to MicroPython](https://github.com/micropython/micropython-lib/tree/master/itertools).

This guide describes a fuller port that has been done to CircuitPython. Additionally, the [itertools recipes functions](https://docs.python.org/3.6/library/itertools.html#itertools-recipes) have been largely made available as well.

Two modules are available as helpers in the CircuitPython Bundle:

**adafruit\_itertools** - a port of the majority of Python's itertools functions.

**adafruit\_itertools\_extras** - a port of many of the itertools recipes functions. These are very useful function that are built on top of the core itertools module.

[Go to GitHub to get the latest CircuitPython library bundle](https://github.com/adafruit/Adafruit_CircuitPython_Bundle/releases/)
## Why itertools?

Similar to how the math module provides functions that operate on numbers, itertools gives you functions that operate on iterators. This lets you code in a more _functional_\* or _dataflow-centric_ way, also sometimes known as _stream-based_ programming..

The Python3/CPython version is implemented in C. The CircuitPython implementation is a pure Python implementation so it's performance isn't nearly as good, but should be adequate in most, if not all, situations. It has the additional benefit of being readily studied and understood.

One of the major advantage of using iterators is that they can be used to compute one value at a time rather than all the values at once. This has several implications:

1. Computing time is spread out as required rather than all at once to populate (for example) a list of values.
2. Since elements are computed as needed, memory to store the entire list is not required. This means you can process more data with less memory. This is very handle in a memory-limited environment.
3. Since time and space is only required for one element at a time, we can deal with sequences of any length, even infinite, without any additional requirements.

<sup>*Functional as in <em>functional programming</em>, not as in <em>it works</em>.</sup>

<sup>Header image icon made by <a href="https://www.flaticon.com/authors/designerz-base" target="_blank">Designerz Base</a> from <a title="Flaticon" href="http://www.flaticon.com/">www.flaticon.com</a></sup>

# Itertools for CircuitPython

## Itertools

![](https://cdn-learn.adafruit.com/assets/assets/000/073/557/medium800/circuitpython_barn-images-12223-unsplash.jpg?1553267129 Photo by Barn Images on Unsplash)

The **adafruit\_itertools** module contains the main iterator building block functions. This provides you with everything you should need to take full advantage of iterators in your code. This section enumerates the functions present in the module, describing each and providing examples. The `list` function is used to pull out the values in cases where an iterator is returned. In cases where the returned iterator is infinite, `take`&nbsp;(from **adafruit\_itertools\_extras** ) is used to pull out a fixed number of values. `take` returns those values in a list.

## `accumulate(iterable, func=lambda x, y: x + y)`

Make an iterator that generates accumulated sums, or accumulated results of other binary functions (specified via the optional `func`argument). If `func` is supplied, it should be a function of two arguments that returns a value of the same type. Elements of `iterable` may be any type that can be accepted as arguments to `func`. (For example, with the default operation of addition, elements may be any addable type.) If `iterable`is empty, no values will be generated.

`>>> list(accumulate(range(10)))`  
`[0, 1, 3, 6, 10, 15, 21, 28, 36, 45]`

`>>> accumulate(range(1, 10), lambda x, y: x * y))`  
`[1, 2, 6, 24, 120, 720, 5040, 40320, 362880]`

## `chain(*iterables)`

Make an iterator that generates elements from the first `iterable` until it is exhausted, then proceeds to the next `iterable`, until all of them are exhausted. Used for treating consecutive sequences as a single sequence.

`>>> list(chain('ABC', 'DEF'))['A', 'B', 'C', 'D', 'E', 'F']`

## `chain_from_iterable(iterables)`

An alternate approach to `chain()`. Iterables to be chanined are generated from a single iterable argument.

So, for example, instead of passing in multiple strings as you would with `chain()`, you can pass in a list of strings.

`>>> list(chain_from_iterable(['ABC', 'DEF']))['A', 'B', 'C', 'D', 'E', 'F']`

## `combinations(iterable, r)`

Generate&nbsp;`r` length subsequences of elements from `iterable`. Combinations are generated in lexicographic sort order (the order they appear in `iterable`). So, if the `iterable` is sorted, the combination tuples will be produced in sorted order.

Elements are treated as unique based on their position, not on their value. So if the input elements are unique, there will be no repeat values in each combination.

`>>> list(combinations('ABCD', 2))``[('A', 'B'), ('A', 'C'), ('A', 'D'), ('B', 'C'), ('B', 'D'), ('C', 'D')]`

`>>> list(combinations(range(4), 3))[(0, 1, 2), (0, 1, 3), (0, 2, 3), (1, 2, 3)]`

## `combinations_with_replacement(iterable, r)`

Generate&nbsp;`r` length subsequences of elements from&nbsp;`iterable` allowing individual elements to be repeated more than once.

Combinations are generated in lexicographic sort order. So, if&nbsp;`iterable` is sorted, the combination tuples will be produced in sorted order.

Elements are treated as unique based on their position, not on their value. So if the input elements are unique, the generated combinations will also be unique.

`>>> list(combinations_with_replacement('ABCD', 2)) `  
`[('A', 'A'), ('A', 'B'), ('A', 'C'), ('A', 'D'), ('B', 'B'), ('B', 'C'), ('B', 'D'), ('C', 'C'), ('C', 'D'), ('D', 'D')]`

`>>> list(combinations_with_replacement(range(4), 3))``[(0, 0, 0), (0, 0, 1), (0, 0, 2), (0, 0, 3), (0, 1, 1), (0, 1, 2), (0, 1, 3), (0, 2, 2), (0, 2, 3), (0, 3, 3), (1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 2, 2), (1, 2, 3), (1, 3, 3), (2, 2, 2), (2, 2, 3), (2, 3, 3), (3, 3, 3)]`

## `compress(data, selectors)`

Make an iterator that filters elements from `data` returning only those that have a corresponding element in `selectors` that evaluates to `True`. Stops when either the `data` or `selectors` iterables has been exhausted.

`>>> list(compress('ABCDEF', [1,0,1,0,1,1]))['A', 'C', 'E', 'F']`

## ` count(start=0, step=1)`

Make an infinite iterator that returns evenly spaced values starting with number `start`. The spacing between values is set by `step`. Often used as an argument to `map()` to generate consecutive data values. Also, used with `zip()` to add sequence numbers.

`>>> take(5, count())`  
`[0, 1, 2, 3, 4]`  
`>>> take(5, count(3))`  
`[3, 4, 5, 6, 7]`  
`>>> take(5, count(1, 2))`  
`[1, 3, 5, 7, 9]`

## ` cycle(iterable)`

Make an iterator returning elements from the `iterable` and saving a copy of each. When the iterable is exhausted, return elements from the saved copy. Repeats indefinitely.

`>>> take(10, cycle("ABCD"))`  
`['A', 'B', 'C', 'D', 'A', 'B', 'C', 'D', 'A', 'B']`

## `dropwhile(predicate, iterable)`

Make an iterator that drops elements from `iterable` as long as&nbsp;`predicate` is true; afterwards, generates every element. Note, the iterator does not produce any output until `predicate` first becomes false, so it may have a lengthy start-up time.

`>>> list(dropwhile(lambda x: x<5, [1,4,6,4,1]))`  
`[6, 4, 1]`

## `filterfalse(predicate, iterable)`

Make an iterator that filters elements from `iterable` generating only those for which `predicate` is `False`. If `predicate` is `None`, return the items that are logically false, i.e. `bool(x)` evaluates to `False`.

`>>> list(filterfalse(lambda x: x%2, range(10)))`  
`[0, 2, 4, 6, 8]`

## `groupby(iterable, key_func=None)`

Make an iterator that generates consecutive keys and groups from `iterable`. `key_func` is a function computing a key value for each element. If not specified or is `None`, `key_func` defaults to an identity function that generates the element unchanged. Generally, it is desirable that `iterable` is already sorted on the same key function.

The operation of `groupby()` is similar to the **uniq** filter in Unix. It generates a break or new group every time the value of the key function changes (which is why it is usually necessary to have sorted the data using the same key function).

The returned group is itself an iterator that shares the underlying iterable with `groupby()`. Because the source is shared, when the `groupby()` object is advanced, the previous group is no longer visible. So, if that data is needed later, it should be stored as a list, like so:

`groups = []`  
`uniquekeys = []`  
`data = sorted(data, key=keyfunc)`  
`for k, g in groupby(data, keyfunc):`  
`    groups.append(list(g)) # Store group iterator as a list`  
`    uniquekeys.append(k)`

`>>> [k for k, g in groupby('AAAABBBCCDAABBB')]`  
`['A', 'B', 'C', 'D', 'A', 'B']`  
`>>> [list(g) for k, g in groupby('AAAABBBCCD')]`  
`[['A', 'A', 'A', 'A'], ['B', 'B', 'B'], ['C', 'C'], ['D']]`

Let's do something a bit more useful. Say we have a list of numbers and we want to divide them up by some criteria. For simplicity let's go with odd/even. We can write a classifier function to do this:

`>>> def even_odd(x):`  
`...     if x % 2 == 0:`  
`...         return 'even'`  
`...     else:`  
`...         return 'odd'`

`>>> even_odd(2)'even'>>> even_odd(5)'odd'`

Next, let's create some random integers:

`numbers = list(repeatfunc(lambda: random.randint(0, 100), 25)))>>> numbers[12, 17, 0, 82, 37, 34, 3, 41, 53, 60, 62, 35, 27, 75, 43, 31, 98, 56, 97, 26, 73, 43, 62, 74, 72]```

We can go ahead and group those using our even/odd function:

`>>> for k, g in groupby(numbers, even_odd):`  
`...     print('{0}: {1}'.format(k, list(g)))`  
`...`  
`even: [12]`  
`odd: [17]`  
`even: [0, 82]`  
`odd: [37]`  
`even: [34]`  
`odd: [3, 41, 53]`  
`even: [60, 62]`  
`odd: [35, 27, 75, 43, 31]`  
`even: [98, 56]`  
`odd: [97]`  
`even: [26]`  
`odd: [73, 43]`  
`even: [62, 74, 72]`

Possibly useful, depending on the need, but we might want one group of even numbers, and one of odd. To get that we need to sort them based on our grouping function:

`>>> sorted(numbers, key=even_odd)`  
`[72, 98, 62, 60, 56, 74, 62, 26, 0, 34, 82, 12, 97, 41, 17, 73, 43, 37, 35, 3, 53, 27, 31, 43, 75]`

We can now group that:

`>>> for k, g in groupby(sorted(numbers, key=even_odd), even_odd):`  
`...     print('{0}: {1}'.format(k, list(g)))`  
`...`  
`even: [72, 98, 62, 60, 56, 74, 62, 26, 0, 34, 82, 12]`  
`odd: [97, 41, 17, 73, 43, 37, 35, 3, 53, 27, 31, 43, 75]`

``

## `islice(iterable, start, stop=None, step=1)`

Make an iterator that generates selected elements from `iterable`.

If `start` is non-zero and stop is unspecified, then the value for start is used as stop, and start is taken to be 0. Thus the supplied value specifies how many elements are to be generated, starting the the first one. In this sense, it functions as `take`.

If stop is specified, then elements from `iterable` are skipped until `start` is reached. Afterward, elements are generated consecutively unless `step` is set higher than one which results in items being skipped. If `stop` is None, then iteration continues until `iterable` is exhausted, if at all; otherwise, it stops at the specified position. If stop is specified and is not None, and is not greater than start then nothing is generated.

Unlike regular slicing, `islice()` does not support negative values for `start`, `stop`, or `step`. It can be used to extract related fields from data where the internal structure has been flattened (for example, a multi-line report may list a name field on every third line).

`>>> list(islice('ABCDEF', 3))`  
`['A', 'B', 'C']`  
`>>> list(islice('ABCDEF', 3, stop=None))`  
`['D', 'E', 'F']`  
`>>> list(islice('ABCDEF', 3, stop=2))`  
`[]`  
`>>> list(islice('ABCDEF', 3, stop=4))`  
`['D']>>> list(islice('ABCDEF', 0, stop=None, step=2))['A', 'C', 'E']```

## `permutations(iterable, r=None)`

Return successive r length permutations of elements in `iterable`.

If `r` is not specified or is `None`, then it defaults to the length of&nbsp;`iterable` and all possible full-length permutations are generated.

Permutations are generated in lexicographic sort order. So, if&nbsp;`iterable` is sorted, the permutation tuples will be produced in sorted order.

Elements are treated as unique based on their position, not on their value. So if the input elements are unique, there will be no repeat values in each permutation.

`>>> list(permutations('ABCD', 2))`  
`[('A', 'B'), ('A', 'C'), ('A', 'D'), ('B', 'A'), ('B', 'C'), ('B', 'D'), ('C', 'A'), ('C', 'B'), ('C', 'D'), ('D', 'A'), ('D', 'B'), ('D', 'C')]`  
`>>> list(permutations(range(3)))`  
`[(0, 1, 2), (0, 2, 1), (1, 0, 2), (1, 2, 0), (2, 0, 1), (2, 1, 0)]`

## `product(*iterables, repeat=1)`

Cartesian product of `iterables`.

Roughly equivalent to nested for-loops in a generator expression. For example, `product(A, B)` generates the same as `((x,y) for x in A for y in``B)`.

The nested loops cycle like an odometer with the rightmost element advancing on every iteration. This pattern creates a lexicographic ordering so that if `iterables` are sorted, the product tuples  
are emitted in sorted order.

To compute the product of an iterable with itself, specify the number of repetitions with the optional repeat keyword argument. For example, `product(A, repeat=4)` means the same as `product(A, A, A, A)`.

`>>> list(product('ABCD', 'xy'))`  
`[('A', 'x'), ('A', 'y'), ('B', 'x'), ('B', 'y'), ('C', 'x'), ('C', 'y'), ('D', 'x'), ('D', 'y')]`  
`>>> list(product(range(2), repeat=3))`  
`[(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1)]`

## ` repeat(object, times=None)`

Make an iterator that generates&nbsp;`object` over and over again. Runs indefinitely unless the `times` argument is specified. Used as argument to`map()` for invariant parameters to the called function. Also used with `zip()`to create an invariant part of a tuple record.

`>>> list(repeat(1, 5))`  
`[1, 1, 1, 1, 1]`

## `starmap(function, iterable)`

Make an iterator that computes `function` using arguments obtained from  
`iterable`. Used instead of `map()` when argument parameters are already  
grouped in tuples from a single iterable (the data has been “pre-zipped”).  
The difference between `map()` and `starmap()` parallels the distinction between  
`function(a,b)` and `function(*c)`.

`>>> list(map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6]))`  
`[5, 7, 9]`  
`>>> list(starmap(lambda x, y: x + y, zip([1, 2, 3], [4, 5, 6])))`  
`[5, 7, 9]`  
`>>> list(starmap(lambda x, y: x + y, [[1, 4], [2, 5], [3, 6]]))`  
`[5, 7, 9]`

## `takewhile(predicate, iterable)`

Make an iterator that generates elements from `iterable` as long as `predicate` is true when applied to them.

`>>> list(takewhile(lambda x: x<5, [1,4,6,4,1]))`  
`[1, 4]`

## `tee(iterable, n=2)`

Return `n` independent iterators from a single `iterable`. The resulting iterators _contain_ the elements from the original by generate them completely independently.

`>>> a, b = tee("ABCDE", 2)`  
`>>> next(a)`  
`'A'`  
`>>> next(b)`  
`'A'`  
`>>> take(2, a)`  
`['B', 'C']`  
`>>> take(3, b)`  
`['B', 'C', 'D']`

## `zip_longest(*iterables, fillvalue=None)`

Make an iterator that aggregates elements from each of the `iterables`. If the `iterables` are of uneven length, missing values are filled-in with `fillvalue`. Iteration continues until the longest iterable is exhausted. Contrast this with the builtin function `zip` which will stop when the _shortest_ iterable is exhausted.

`>>> list(zip_longest('ABCD', 'xy', fillvalue='-'))`  
`[('A', 'x'), ('B', 'y'), ('C', '-'), ('D', '-')]`

# Itertools for CircuitPython

## Extras

![](https://cdn-learn.adafruit.com/assets/assets/000/073/558/medium800/circuitpython_rawpixel-648581-unsplash.jpg?1553267280 Photo by rawpixel on Unsplash)

The CPython documentation provides a selection of useful functions that are built on top of itertools. These have been made available in the **adafruit\_itertools\_extras** module. not only are these useful, but looking through the implementations is instructive on how iterators and the itertools functions can be used.

## `all_equal(iterable)`

Returns `True` if all the elements of `iterable` are equal to each other.

`>>> all_equal([6, 2*3, 12//2, 4+2])``True`  
`>>> all_equal([6, 2*3, 12//2, 5, 4+2])``False`

## `dotproduct(vec1, vec2)`

Compute the dot product of two vectors. This is the sum of the products of pairs (one item from each vector)

`>>> dotproduct([1, 2, 3], [1, 2, 3])``14`

This is equivalent to (1 \* 1) + (2 \* 2) + (3 \* 3) = 1 + 4 + 9 = 14

## `first_true(iterable, default=False, pred=None)`

Returns the first _truthy_ value in `iterable`, i.e. the first value `x` for which `bool(x)` evaluates to `True`.

If no true value is found, returns `default`.

If `pred` is not `None`, returns the first item for which `pred(item)` is true.

`first_true([a,b,c], x)` is equivalent&nbsp;`a or b or c or x`  
`first_true([a,b], x, f)`&nbsp;is equivalent&nbsp;`a if f(a) else b if f(b) else x`

## `flatten(iterable_of_iterables)`

Flatten one level of nesting.

`>>> list(flatten(['ABC', 'DEF']))``['A', 'B', 'C', 'D', 'E', 'F']`

`>>> list(flatten([[1, [2, 3]], [4, [5], 6]]))`  
`[1, [2, 3], 4, [5], 6]`

## `grouper(iterable, n, fillvalue=None)`

Collect data from `iterable` into fixed-length chunks of size `n`. The final chunk will be filled in with `fillvalue` if iterable has less that a multiple of `n` elements.

`>>> list(grouper('ABCDEFG', 3, 'x'))`  
`[('A', 'B', 'C'), ('D', 'E', 'F'), ('G', 'x', 'x')]`  
`>>> list(grouper('ABCDEFG', 2))`  
`[('A', 'B'), ('C', 'D'), ('E', 'F'), ('G', None)]`

## `iter_except(func, exception)`

Call `func` repeatedly until `exception` is raised, yielding the result of each call.

`>>> s = [1, 2, 3]`  
`>>> s.pop()`  
`3`  
`>>> s.pop()`  
`2`  
`>>> s.pop()`  
`1`  
`>>> s.pop()`  
`Traceback (most recent call last):`  
`File "", line 1, in `  
`IndexError: pop from empty list`  
  
`>>> s = [1, 2, 3]`  
`>>> list(iter_except(s.pop, IndexError))`  
`[3, 2, 1]`

## `ncycles(iterable, n)`

Returns an iterable from `n` copies of the elements from `iterable`.

`>>> list(ncycles('ABC', 3))`  
`['A', 'B', 'C', 'A', 'B', 'C', 'A', 'B', 'C']`

## ` nth(iterable, n, default=None)`

Returns the `nth`&nbsp;(0-based) item of `iterable` or a `default` value (`None` by default) if `n` is out of range.

`>>> nth('ABCD', 0)`  
`'A'`  
`>>> nth('ABCD', 3)`  
`'D'`  
`>>> nth('ABCD', 4)`  
`>>> nth('ABCD', 4, 'Z')`  
`'Z'`

## `padnone(iterable)`

Returns the elements from `iterable` and then returns `None` indefinitely.

Useful for emulating the behavior of the built-in `map()` function.

`take(5, padnone([1, 2, 3]))` -\> `1 2 3 None None`

## `pairwise(iterable)`

Pair up values in `iterable`.

`>>> list(pairwise(range(5)))`  
`[(0, 1), (1, 2), (2, 3), (3, 4)]`

## `partition(predicate, iterable)`

Use `predicate` to partition entries in `iterable` into false entries and true entries.

`>>> t, f = partition(lambda x: x % 2, range(10))`  
`>>> list(t)`  
`[0, 2, 4, 6, 8]`  
`>>> list(f)`  
`[1, 3, 5, 7, 9]`

## `prepend(value, iterator)`

Prepend a `value` in front of `iterator`.

`prepend(1, [2, 3, 4])` -\> `1 2 3 4`

## `quantify(iterable, predicate=bool)`

Count the number of elements in `iterable` for which `predicate` is `True`. By default it counts elements `x` where&nbsp;`bool(x)` results in `True`.

`>>> quantify([2, 56, 3, 10, 85], lambda x: x >= 10)`  
`3`

## `repeatfunc(func, times=None, *args)`

Lazily repeat calls to `func`&nbsp;a number of times set by&nbsp;`times` with specified arguments (if any). If `times` is `None` (the default), call it infinitely. Return values from the function are generated by the resulting iterator.

`>>> take(5, repeatfunc(random.random))`  
`[0.777482, 0.718999, 0.953034, 0.388373, 0.982035]`

## `roundrobin(*iterables)`

Return an iterable created by repeatedly picking a value from each of `iterables`, in order. As individual iterables are exhausted, they are dropped from the picking.

`>>> list(roundrobin('ABC', 'D', 'EF'))`  
`['A', 'D', 'E', 'B', 'F', 'C']`

## `tabulate(function, start=0)`

Return an iterable populated by applying `function` to a sequence of consecutive integers, starting at `start` (defaults to 0).

`>>> take(5, tabulate(lambda x: x * x))`  
`[0, 1, 4, 9, 16]`

## `tail(n, iterable)`

Return an iterator over the last `n` items of `iterable`. This only works if `iterable` is finite.

`>>> list(tail(3, 'ABCDEFG'))`  
`['E', 'F', 'G']`

## `take(n, iterable)`

Return first `n` items of `iterable` as a list.

`>>> take(3, 'ABCDEF')`  
`['A', 'B', 'C']`

# Itertools for CircuitPython

## Examples

![](https://cdn-learn.adafruit.com/assets/assets/000/073/578/medium800/circuitpython_conveyorbelt.jpg?1553356149 By wikipedia user HH58 CC BY-SA 4.0)

## Assembling Data

Let's say you are logging readings from a temperature sensor (the Si7021 for example). For some reason you need a reading number and a timestamp in addition to the reading, rounded to an integer.

You could do this the _standard_ way:

```auto
import time
import board
import busio
import adafruit_si7021

i2c = busio.I2C(board.SCL, board.SDA)
sensor = adafruit_si7021.SI7021(i2c)

def read_temperature():
    return sensor.temperature

def now():
    return time.monotonic()

def non_iter():
    reading_number = 1
    while True:
        datapoint = (count, now(), int(read_temperature()))
        print(datapoint)
        count += 1
        time.sleep(20.0)
```

Using iterators we can do this in a slightly different way:

```auto
import time
import board
import busio
import adafruit_si7021
from adafruit_itertools import count
from adafruit_itertools_extras import repeatfunc, roundrobin

i2c = busio.I2C(board.SCL, board.SDA)
sensor = adafruit_si7021.SI7021(i2c)

def read_temperature():
    return sensor.temperature

def now():
    return time.monotonic()

def iter():
    datapoints = zip(count(1), repeatfunc(now), map(int, repeatfunc(read_temperature)))
    while True:
        print(next(datapoints))
        time.sleep(20.0)
```

The iterator based version is a couple lines shorter, but what's the advantage beyond that?

In the non-iterator version the data point is being constructed in the loop each time. In the iterator-based version, an iterator is constructed that will generate data tuples as requested. More importantly tuple generation is encapsulated in an iterator. That means it can be stored in a variable (as it is in this example, in `datapoints`), but also passed as an argument. This last thing is significant in that it lets you separate (_decouple_ in programming lingo) the generate of data, from the use of it. The code in the loop doesn't care how to make a datapoint, it just asks for the next one. The iterator doesn't care how or when it's elements are being used, it just hands back the next one when asked. It doesn't even really know or care how the elements are being made. there's code that puts together the iterator, and other code that asks for elements. All the iterator does is construct a single element based on how it's been wired up. As such it serves as a great intermediary between the code that knows how to get/make the data and the code that knows how to use it.

## Filtering by Divisibility

Here's an example from SICP\*: let's say for some reason you need numbers that are not divisible by 7.&nbsp;

First we can make a simple predicate to determine divisibility:

`def divisible(x, y):`  
`    return (x % y) == 0`

To get numbers that are not divisible by 7 we can use `filterfalse` with a lambda:

`>>> list(filterfalse(lambda x: divisible(x, 7), range(30)))`  
`[1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 22, 23, 24, 25, 26, 27, 29]`

In practice we probably don't want to pass in a range, but rather an infinite sequence of integers:

`>>> take(20, filterfalse(lambda x: divisible(x, 7), count()))`  
`[1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 22, 23]`

The next step would be to package this up to pass to whatever function wants a sequence of integers that are not divisible by 7:

`>>> no_sevens = filterfalse(lambda x: divisible(x, 7), count())`  
`>>> take(20, no_sevens)`  
`[1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 22, 23]`

We can generalize by using a function that returns a lambda to construct the test:

`>>> def divisibility_checker(x):`  
`...     return lambda n: (n % x) == 0``>>> by7 = divisibility_checker(7)``>>> no_sevens = filterfalse(by7, count())```  
`>>> take(20, no_sevens)`  
`[1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 22, 23]`

Once more, this lets us separate the construction of the sequence from the use of it.

Using iterators also let you directly make requests like:

- Give me the next 5 items.
- Give me the 10th item.
- Skip 10 items and give me 5.
- Ignore items until one is over 100.

And so on.&nbsp; All from the same iterator. All the sequencing, traversal, and computation is bundled up in the iterator and all your code has to do is ask for items from it. This can help make your code simpler and clearer.

<sup>* The Structure and Implementation of Computer Programs ... one book every programmer should read and study.</sup>


## Related Guides

- [Adafruit 2.13" 250x122 Quad-Color eInk](https://learn.adafruit.com/adafruit-2-13-250x122-quad-color-eink.md)
- [Adafruit 2.7" eInk Display Breakouts and Shields](https://learn.adafruit.com/adafruit-2-7-eink-display-breakouts-and-shields.md)
- [ScoutMakes Robot Kit](https://learn.adafruit.com/scoutmakes-robot-kit.md)
- [CircuitPython 101: State Machines, Two Ways](https://learn.adafruit.com/circuitpython-101-state-machines.md)
- [Super Nintendo USB Controller](https://learn.adafruit.com/super-nintendo-usb-controller.md)
- [Using the Adafruit FunHouse with Home Assistant](https://learn.adafruit.com/using-the-adafruit-funhouse-with-home-assistant.md)
- [Adafruit NeoKey Trinkey](https://learn.adafruit.com/adafruit-neokey-trinkey.md)
- [Generating Text with ChatGPT, Pico W & CircuitPython](https://learn.adafruit.com/generating-text-with-chatgpt-pico-w-circuitpython.md)
- [Getting Started with HalloWing for Hackaday Supercon Attendees](https://learn.adafruit.com/getting-started-with-hallowing-for-hackaday-supercon-attendees.md)
- [NeoTrellis MIDI Feedback Controller](https://learn.adafruit.com/neotrellis-midi-feedback-controller.md)
- [Proximity Based Lighting](https://learn.adafruit.com/proximity-based-lighting.md)
- [Audio Synthesis with CircuitPython synthio](https://learn.adafruit.com/audio-synthesis-with-circuitpython-synthio.md)
- [Circuit Playground Express Perk-up Ears](https://learn.adafruit.com/perk-up-ears.md)
- [PM2.5 Air Quality Sensor](https://learn.adafruit.com/pm25-air-quality-sensor.md)
- [Getting Started with Raspberry Pi Pico and CircuitPython](https://learn.adafruit.com/getting-started-with-raspberry-pi-pico-circuitpython.md)
