# Itertools¶

## itertools.combinations: A better way to iterate through a pair of values in a Python list¶

If you want to iterate through a pair of values in a list and the order does not matter (`(a,b)` is the same as `(b, a)`), a naive approach is to use two for-loops.

```num_list = [1, 2, 3]
```
```for i in num_list:
for j in num_list:
if i < j:
print((i, j))
```
```(1, 2)
(1, 3)
(2, 3)
```

However, using two for-loops is lengthy and inefficient. Use `itertools.combinations` instead:

```from itertools import combinations

comb = combinations(num_list, 2) # use this
for pair in list(comb):
print(pair)
```
```(1, 2)
(1, 3)
(2, 3)
```

## itertools.product: Nested For-Loops in a Generator Expression¶

Are you using nested for-loops to experiment with different combinations of parameters? If so, use `itertools.product` instead.

`itertools.product` is more efficient than nested loop because `product(A, B)` returns the same as `((x,y) for x in A for y in B)`.

```from itertools import product

params = {
"learning_rate": [1e-1, 1e-2, 1e-3],
"batch_size": [16, 32, 64],
}

for vals in product(*params.values()):
combination = dict(zip(params.keys(), vals))
print(combination)
```
```{'learning_rate': 0.1, 'batch_size': 16}
{'learning_rate': 0.1, 'batch_size': 32}
{'learning_rate': 0.1, 'batch_size': 64}
{'learning_rate': 0.01, 'batch_size': 16}
{'learning_rate': 0.01, 'batch_size': 32}
{'learning_rate': 0.01, 'batch_size': 64}
{'learning_rate': 0.001, 'batch_size': 16}
{'learning_rate': 0.001, 'batch_size': 32}
{'learning_rate': 0.001, 'batch_size': 64}
```