In our last snippet post we a quick look at the `product` function found in the `itertools` module. Today we're going to look at a few more combinatoric iterators from the `itertools` module: `permutations`, `combinations`, and `combinations_with_replacement`.

First, let's look at `permutations`. `permutations` is concerned with finding all of the possible orderings for a given collection of items. For example, if we have the string `"ABC"`, permutations will find all of the ways we can reorder the letters in this string, so that each order is unique.

``````from itertools import permutations

p_1 = permutations("ABC")
# ('A', 'B', 'C') ('A', 'C', 'B') ('B', 'A', 'C') ('B', 'C', 'A')
# ('C', 'A', 'B') ('C', 'B', 'A')
``````

By default, `permutations` returns different orderings for the entire collection, but we can use the optional `r` parameter to limit the function to finding shorter permutations.

``````p_2 = permutations("ABC", r=2)
# ('A', 'B') ('A', 'C') ('B', 'A') ('B', 'C') ('C', 'A') ('C', 'B')
``````

Providing an `r` value greater than the length of the collection passed into `permutations` will yield an empty permutations object.

Now let's take a look at `combinations`. `combinations` returns an iterable object containing unique combinations of elements from a provided collection. Note that `combinations` isn't concerned with the order of elements, so `combinations` will treat `('A', 'B')` as being identical to `('B', 'A')` in its results.

The length of the resulting combinations is controlled by the `r` parameter once again, but in the case of `combinations`, this argument is mandatory.

``````from itertools import combinations

c_1 = combinations("ABC", r=2)
# ('A', 'B') ('A', 'C') ('B', 'C')

c_2 = combinations("ABC", r=3)
# ('A', 'B', 'C')
``````

It is possible to get duplicate elements back from `combinations`, but only if the provided iterable contains multiple instances of a given element. `(1, 2, 3, 1)`, for example.

``````c_3 = combinations((1, 2, 3, 1), r=2)
# (1, 2) (1, 3) (1, 1) (2, 3) (2, 1) (3, 1)
``````

In this case `(1, 2)` and `(2, 1)` are not simply the same elements in a different order, the 1s are in fact different elements in the original collection.

It's possible to include instances where an item is paired with itself using the `combinations_with_replacement` function. It works just like `combinations`, but will also match every element to itself.

``````from itertools import combinations, combinations_with_replacement

c_4 = combinations((1, 2, 3), r=2)
# (1, 2) (1, 3) (2, 3)

c_5 = combinations_with_replacement((1, 2, 3), r=2)
# (1, 1) (1, 2) (1, 3) (2, 2) (2, 3) (3, 3)
``````

That wraps up the combinatoric iterators! I hope you learnt something new, and be sure to check out the official documentation for more details.

If you're looking to upgrade your Python skills even further, we'd love to have you on our Complete Python Course. You can follow the link in this post to get the course for just \$9.99, and there's also a 30 day money back guarantee.

We'll be back next Monday with another snippet post, this time covering a very interesting type of collection. Follow us on Twitter so you don't miss out!