Skip to Content
Course content

130: itertools for Extended Iterator Methods

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was tasked with building a telemetry analyzer that processed a stream of sensor readings. I needed to find every instance where the temperature jumped by more than 5 degrees between two consecutive readings. My first instinct was to use a while loop with an index, but that felt clumsy and error-prone. Then I tried ziping the iterator with a skipped version of itself—iter.zip(iter.skip(1))—which is the "standard" way to do it, but the syntax is a bit of a mouthful and it requires the iterator to be Clone. I spent an hour fighting with the borrow checker and iterator types before I remembered that itertools exists. Once I swapped in tuple_windows(), the logic collapsed from ten lines of boilerplate into a single, readable chain.

Filling the Gaps in the Standard Library

Rust's standard library is intentionally lean. The std::iter module provides the essential building blocks—map, filter, fold—but it doesn't include every possible iterator operation because the language maintainers want to avoid bloat. This is where the itertools crate comes in. It's essentially the "standard library extension" for iterators. If you find yourself writing a complex loop to handle something that feels like it should be a single method call, there is a 90% chance itertools already has it.

To use it, you'll add itertools = "0.13" to your Cargo.toml. Most of its functionality is provided via the Itertools trait, which you import into your scope to "unlock" these methods on any existing iterator.

Windowing and Grouping Data

One of the most powerful tools in the crate is tuple_windows(). As I mentioned in my telemetry example, this allows you to look at a sliding window of elements. Instead of managing indices, you get a stream of tuples. If you want to see pairs, you use tuple_windows::<(T, T)>(); for triplets, tuple_windows::<(T, T, T)>(). It's incredibly clean for calculating deltas or checking for sequences.

use itertools::Itertools;

let readings = vec![20.1, 20.5, 25.2, 24.8, 31.0];
// Find jumps greater than 5 degrees
let spikes: Vec<_> = readings.iter()
    .tuple_windows()
    .filter(|(prev, next)| (next - prev).abs() > 5.0)
    .collect();


Then there's group_by(). Unlike the grouping functions in languages like SQL or Python's itertools, Rust's group_by expects the input to be sorted by the key you're grouping by. It yields a series of groups, where each group is itself an iterator. It's a bit more complex to use than a simple map, but it's indispensable when you need to process contiguous blocks of similar data without allocating a HashMap.

More Ergonomic Combinations

You've likely used zip to combine two iterators. But if you have three or four iterators, nesting zip(zip(a, b), c) becomes a nightmare of nested tuples. itertools provides the izip! macro, which lets you zip any number of iterators into a single flat tuple. It's a massive quality-of-life improvement.

I also frequently rely on unique(). In the standard library, removing duplicates usually requires collecting into a HashSet and then back into a Vec, which destroys the original order of your elements. unique() maintains the order while filtering out duplicates on the fly, provided the elements implement Eq and Hash. Honestly, I can't imagine writing a data processing pipeline in Rust without it.




📋 Practical Task

Build a Stock Price Volatility Alert

You are building a monitoring tool for a trading desk. You are given a Vec of stock prices. Your goal is to identify "volatile swings"—defined as any two consecutive prices where the change (up or down) is greater than 2% of the previous price.

Requirements:

  • Add the itertools crate to your project.
  • Use tuple_windows() to iterate through the prices in pairs.
  • Calculate the percentage difference between the current and next price.
  • Collect the pairs that exceed the 2% threshold into a Vec of tuples.
  • Print only the unique() prices that were involved in these volatile swings (to avoid listing the same price twice if it was part of two different swings).
Starter Data: let prices = vec![100.0, 101.0, 103.5, 103.0, 106.0, 105.5, 110.0];
Rating
0 0

There are no comments for now.

to be the first to leave a comment.