# Python’s “Itertools”¶

Python has an itertools module, which provides a core set of fast, memory-efficient tools for creating iterators. The majority of these functions create generators. We will briefly showcase a few itertools. It is hard to overstate the utility of this module - it is strongly recommended that you take some time to see what it has in store.

Actually, there are three functions that belong in itertools, but are so
useful that they are included in Python by default, and do not need to
be imported. It is essential that `range`

, `enumerate`

, and `zip`

become tools that you are comfortable using.

**range**

generate a sequence of integers in the specified “range”:

```
# will generate 0.. 1.. 2.. ... 8.. 9
range(10)
```

**enumerate**

“enumerate” the items in an iterable:

```
# will generate (0, 'apple').. (1, 'banana').. (2, 'cat').. (3, 'dog')]
enumerate(["apple", "banana", "cat", "dog"])
```

**zip**

“zip” together the corresponding elements of several iterables into tuples:

```
>>> names = ["Angie", "Brian", "Cassie", "David"]
>>> exam_1_scores = [90, 82, 79, 87]
>>> exam_2_scores = [95, 84, 72, 91]
# will generate ('Angie', 90, 95).. ('Brian', 82, 84).. ('Cassie', 79, 72).. ('David', 87, 91)]
>>> zip(names, exam_1_scores, exam_2_scores)
<zip at 0x20de1082608>
```

The following are some of the many useful tools provided by the
`itertools`

module:

**itertools.chain**

“chain” together multiple iterables, end-to-end:

```
>>> from itertools import chain
>>> gen_1 = range(0, 5, 2) # 0.. 2.. 4
>>> gen_2 = (i**2 for i in range(3, 6)) # 9.. 16.. 25
>>> iter_3 = ["moo", "cow"]
>>> iter_4 = "him"
# will generate: 0.. 2.. 4.. 9.. 16.. 25.. 'moo'.. 'cow'.. 'h'.. 'i'.. 'm'
>>> chain(gen_1, gen_2, iter_3, iter_4)
<itertools.chain at 0x20de109ec18>
```

**itertools.combinations** Generate all length-n “combinations” from an
iterable:

```
>>> from itertools import combinations
# will generate: (0, 1, 2).. (0, 1, 3).. (0, 2, 3).. (1, 2, 3)
>>> combinations([0, 1, 2, 3], 3) # generate all length-3 combinations from [0, 1, 2, 3]
<itertools.combinations at 0x20de10a7728>
```

**Reading Comprehension: Itertools I**

Using the `itertools.combinations`

function, find the probability that
two randomly drawn items from the list
`["apples", "bananas", "pears", "pears", "oranges"]`

would yield a
combination of “apples” and “pears”.

**Reading Comprehension: Itertools II**

Given the list `x_vals = [0.1, 0.3, 0.6, 0.9]`

, create a generator,
`y_gen`

, that will generate the y-value \(y = x^2\) for each value
of \(x\). Then, using `zip`

, create a list of the \((x, y)\)
pairs, each pair stored in a tuple.

## Reading Comprehension: Solutions¶

**Itertools I: Solution**

```
>>> from itertools import combinations
>>> ls = ["apples", "bananas", "pears", "pears", "oranges"]
>>> comb_ls = list(combinations(ls, 2))
>>> comb_ls.count(("apples", "pears")) / len(comb_ls)
0.2
```

**Itertools II: Solution**

```
>>> x_vals = [0.1, 0.3, 0.6, 0.9]
>>> y_gen = (x**2 for x in x_vals)
>>> list(zip(x_vals, y_gen))
[(0.1, 0.01), (0.3, 0.09), (0.6, 0.36), (0.9, 0.81)]
```

In this instance, the use of `zip`

is a bit contrived. We could have
foregone creating `y_gen`

by just using the following
list-comprehension:

```
>>> x_vals = [0.1, 0.3, 0.6, 0.9]
>>> [(x, x**2) for x in x_vals]
[(0.1, 0.01), (0.3, 0.09), (0.6, 0.36), (0.9, 0.81)]
```