Day 7 - itertools

Relevancy: 1.9 stable

The itertools crate contains several utility functions and macros inspired by Haskell and Python itertools. As you can guess from the name, these have to do with iteration and iterators.

To use itertools, add the following dependency declaration to Cargo.toml:

[dependencies]
itertools = "~0.0.4"

We'll start from the helper functions and cover the macros later.

foreach

This and a few other functions live in the Itertools trait, so we need to bring them into scope by placing use itertools::Itertools in our module.

foreach() is very simple conceptually. It consumes the (mutable) iterator, calling a closure witch each of the elements. The return type is () (unit), meaning that foreach() usually should be at the end of a call chain, like below:

day7.rs

let mut words = "hello supercalifragilisticexpialidocious programmer".split(|c| c == ' ');
    words.foreach(|word| println!("{} is {} characters long", word, word.len()));

As you can see, foreach() is similar to the map() method from the standard library, however map returns another iterator. Therefore it's lazy and allows for further method chaining, while foreach is eager and has the final word.

interleave and intersperse

interleave() is somewhat similar to zip(). But when zip builds tuples from two iterators, interleave yields the values alternating between both iterators.

day7.rs

let even = (1..10).map(|x| x * 2);
    let odd = (1..5).map(|x| x * 2 + 1);
    println!("{:?}", even.interleave(odd).collect::<Vec<_>>());

The result:

$ cargo run
[2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18]

As you can see, the iterators don't have to contain the same number of elements. In that case interleave continues to emit values from the other iterator, after it consumes the "shorter" one.

In a manner similar to its Haskell counterpart, intersperse takes a single value and an iterator (implicitly as the self argument) and emits the given value between every element of the wrapped iterator. For example:

day7.rs

println!("{:?}", (1..10).intersperse(15).collect::<Vec<_>>());

Output:

$ cargo run
[1, 15, 2, 15, 3, 15, 4, 15, 5, 15, 6, 15, 7, 15, 8, 15, 9]

iproduct!

Let's now turn our attention to macros provided by itertools. Sometimes there is a need to iterate over a cartesian product of some lists/arrays/vectors. Usually it involves two nested loops; however we can use the iproduct() macro to simplify it to a single for loop.

day7.rs

let numbers = 1..4;
    let chars = vec!['a', 'b', 'c'];
    for (i, c) in iproduct!(numbers, chars.iter()) {
        println!("{}: {}", i, c);
    }