How do I use iterator utilities with itertools in Rust?

Walkthrough

The itertools crate extends Rust's standard iterator functionality with powerful combinator methods. It provides adapter methods like interleave(), cartesian_product(), unique(), group_by(), and many more that make complex iteration patterns clean and readable. The crate is designed to work seamlessly with standard iterators—just add use itertools::Itertools; to unlock all methods. It's essential for data processing pipelines, algorithm implementations, and any code that works with sequences.

Key concepts:

  1. Extension traitItertools adds methods to all iterators
  2. Lazy evaluation — most adapters are lazy like standard iterators
  3. Composable — chain multiple itertools methods together
  4. Collect helperscollect_vec(), collect_tuple() for convenient collection
  5. Grouping and batching — powerful grouping operations

Code Example

# Cargo.toml
[dependencies]
itertools = "0.13"
use itertools::Itertools;
 
fn main() {
    let numbers = vec![1, 2, 3, 4, 5];
    
    // Unique pairs
    let pairs: Vec<_> = numbers.iter().tuple_combinations::<(_,_)>().collect();
    println!("Pairs: {:?}", pairs);
}

Getting Started

use itertools::Itertools;
 
fn main() {
    let numbers = vec![1, 2, 3, 4, 5];
    
    // Easy collection
    let doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect();
    println!("Doubled: {:?}", doubled);
    
    // Collect into tuple
    let (first, second): (i32, i32) = numbers.iter().take(2).copied().collect_tuple().unwrap();
    println!("First two: {}, {}", first, second);
    
    // Join elements
    let joined = numbers.iter().join(", ");
    println!("Joined: {}", joined);
}

Unique Elements

use itertools::Itertools;
 
fn main() {
    let numbers = vec![1, 2, 2, 3, 3, 3, 4, 4, 4, 4];
    
    // Get unique elements (preserves order)
    let unique: Vec<_> = numbers.iter().unique().collect();
    println!("Unique: {:?}", unique);
    
    // Unique by key
    let items = vec![("a", 1), ("b", 2), ("a", 3), ("b", 4)];
    let unique_by_key: Vec<_> = items.iter().unique_by(|(key, _)| key).collect();
    println!("Unique by key: {:?}", unique_by_key);
    
    // Count occurrences
    let counts: Vec<_> = numbers.iter().counts().into_iter().collect();
    println!("Counts: {:?}", counts);
}

Grouping Elements

use itertools::Itertools;
 
fn main() {
    let numbers = vec![1, 1, 2, 2, 2, 3, 3, 4, 4, 4, 4];
    
    // Group consecutive identical elements
    let groups: Vec<Vec<_>> = numbers.iter().group_by(|&x| x).into_iter()
        .map(|(key, group)| group.collect())
        .collect();
    
    println!("Groups: {:?}", groups);
    
    // Group by property
    let items = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    let even_odd: Vec<_> = items.iter()
        .group_by(|&x| x % 2)
        .into_iter()
        .map(|(key, group)| (key, group.copied().collect::<Vec<_>>()))
        .collect();
    
    println!("Even: {:?}", even_odd.iter().find(|(k, _)| *k == 0).unwrap().1);
    println!("Odd: {:?}", even_odd.iter().find(|(k, _)| *k == 1).unwrap().1);
}

Chunking and Batching

use itertools::Itertools;
 
fn main() {
    let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    
    // Fixed-size chunks
    let chunks: Vec<Vec<_>> = numbers.iter().chunks(3).into_iter()
        .map(|chunk| chunk.collect())
        .collect();
    println!("Chunks of 3: {:?}", chunks);
    
    // Batching with custom sizes
    let mut batch = numbers.iter().batching(|it| {
        let mut batch = Vec::new();
        while batch.len() < 3 {
            match it.next() {
                Some(x) => batch.push(x),
                None => break,
            }
        }
        if batch.is_empty() { None } else { Some(batch) }
    });
    
    while let Some(b) = batch.next() {
        println!("Batch: {:?}", b);
    }
}

Cartesian Product

use itertools::Itertools;
 
fn main() {
    let letters = vec!['a', 'b'];
    let numbers = vec![1, 2, 3];
    
    // Cartesian product
    let pairs: Vec<_> = letters.iter().cartesian_product(numbers.iter()).collect();
    println!("Pairs: {:?}", pairs);
    
    // Multiple iterators product
    let colors = vec!["red", "blue"];
    let sizes = vec!["S", "M", "L"];
    let products = vec!["shirt", "pants"];
    
    let combinations: Vec<_> = colors.iter()
        .cartesian_product(sizes.iter())
        .cartesian_product(products.iter())
        .collect();
    
    println!("Combinations: {:?}", combinations);
}

Combinations and Permutations

use itertools::Itertools;
 
fn main() {
    let letters = vec!['a', 'b', 'c', 'd'];
    
    // Combinations of size n (order doesn't matter)
    println!("Combinations of 2:");
    for combo in letters.iter().combinations(2) {
        println!("  {:?}", combo);
    }
    
    // Combinations with replacement
    println!("\nCombinations with replacement:");
    for combo in letters.iter().combinations_with_replacement(2) {
        println!("  {:?}", combo);
    }
    
    // Permutations of size n (order matters)
    println!("\nPermutations of 2:");
    for perm in letters.iter().permutations(2) {
        println!("  {:?}", perm);
    }
    
    // All permutations
    println!("\nAll permutations:");
    for perm in letters.iter().permutations(letters.len()) {
        println!("  {:?}", perm);
    }
}

Tuple Combinations

use itertools::Itertools;
 
fn main() {
    let numbers = vec![1, 2, 3, 4];
    
    // All unique pairs
    println!("Pairs:");
    for (a, b) in numbers.iter().tuple_combinations() {
        println!("  ({}, {})", a, b);
    }
    
    // All unique triples
    let letters = vec!['a', 'b', 'c', 'd'];
    println!("\nTriples:");
    for (a, b, c) in letters.iter().tuple_combinations() {
        println!("  ({}, {}, {})", a, b, c);
    }
    
    // Windows as tuples
    println!("\nWindows (consecutive pairs):");
    for (prev, curr) in numbers.iter().tuple_windows() {
        println!("  {} -> {}", prev, curr);
    }
}

Interleaving and Zipping

use itertools::Itertools;
 
fn main() {
    let a = vec![1, 3, 5, 7];
    let b = vec![2, 4, 6, 8];
    
    // Interleave: alternate elements
    let interleaved: Vec<_> = a.iter().interleave(b.iter()).collect();
    println!("Interleaved: {:?}", interleaved);
    
    // Interleave shortest
    let short = vec![1, 3];
    let long = vec![2, 4, 6, 8];
    let interleaved_short: Vec<_> = short.iter().interleave_shortest(long.iter()).collect();
    println!("Interleave shortest: {:?}", interleaved_short);
    
    // Zip longest (fills with None if lengths differ)
    let zipped: Vec<_> = short.iter().zip_longest(long.iter()).collect();
    println!("Zip longest: {:?}", zipped);
    
    // Zip with custom fill
    let zipped_fill: Vec<_> = short.iter().zip_eq(long.iter()).collect();
    println!("Zip eq (requires same length): {:?}", zipped_fill);
}

Merging Sorted Iterators

use itertools::Itertools;
 
fn main() {
    let a = vec![1, 3, 5, 7, 9];
    let b = vec![2, 4, 6, 8, 10];
    
    // Merge two sorted iterators
    let merged: Vec<_> = a.iter().merge(b.iter()).copied().collect();
    println!("Merged: {:?}", merged);
    
    // Merge multiple sorted iterators
    let c = vec![0, 11];
    let d = vec![5, 6];
    let merged_all: Vec<_> = a.iter()
        .merge(b.iter())
        .merge(c.iter())
        .copied()
        .collect();
    println!("Merged all: {:?}", merged_all);
    
    // Merge by key
    let left = vec![(1, "a"), (3, "c"), (5, "e")];
    let right = vec![(2, "b"), (4, "d"), (6, "f")];
    
    let merged_by: Vec<_> = left.iter().merge_by(right.iter(), |l, r| l.0 < r.0).collect();
    println!("Merged by key: {:?}", merged_by);
}

Folding and Reducing

use itertools::Itertools;
 
fn main() {
    let numbers = vec![1, 2, 3, 4, 5];
    
    // Fold results
    let sum: Vec<_> = numbers.iter().copied().fold_results(0, |acc, x| acc + x).collect();
    println!("Sum: {:?}", sum);
    
    // Tree fold (balanced folding)
    let tree_sum = numbers.iter().copied().tree_fold(|a, b| a + b);
    println!("Tree fold sum: {:?}", tree_sum);
    
    // Reduce with state
    let result = numbers.iter().copied().reduce(|acc, x| acc + x);
    println!("Reduce sum: {:?}", result);
    
    // Scan and fold
    let running_sum: Vec<_> = numbers.iter().scan(0, |state, &x| {
        *state += x;
        Some(*state)
    }).collect();
    println!("Running sum: {:?}", running_sum);
}

Taking and Skipping

use itertools::Itertools;
 
fn main() {
    let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    
    // Take while predicate is true, then continue
    let (taken, rest): (Vec<_>, Vec<_>) = numbers.iter()
        .copied()
        .partition(|&x| x < 5);
    
    println!("Taken (<5): {:?}", taken);
    println!("Rest: {:?}", rest);
    
    // Take until predicate is true (exclusive)
    let taken: Vec<_> = numbers.iter().copied().take_while(|&x| x < 5).collect();
    println!("Take while <5: {:?}", taken);
    
    // Skip while predicate is true
    let skipped: Vec<_> = numbers.iter().copied().skip_while(|&x| x < 5).collect();
    println!("Skip while <5: {:?}", skipped);
}

Position and Indices

use itertools::Itertools;
 
fn main() {
    let numbers = vec![10, 20, 30, 40, 50];
    
    // Enumerate starting from 1
    for (i, val) in numbers.iter().enumerate().map(|(i, v)| (i + 1, v)) {
        println!("{}. {}", i, val);
    }
    
    // Find position
    let pos = numbers.iter().position(|&x| x == 30);
    println!("Position of 30: {:?}", pos);
    
    // Positions where predicate is true
    let positions: Vec<_> = numbers.iter()
        .positions(|&x| x > 25)
        .collect();
    println!("Positions > 25: {:?}", positions);
    
    // With index
    for (idx, val) in numbers.iter().with_position() {
        match idx {
            itertools::Position::First => println!("First: {}", val),
            itertools::Position::Middle => println!("Middle: {}", val),
            itertools::Position::Last => println!("Last: {}", val),
            itertools::Position::Only => println!("Only: {}", val),
        }
    }
}

Flattening and Concatenating

use itertools::Itertools;
 
fn main() {
    let nested = vec![vec![1, 2], vec![3, 4], vec![5, 6]];
    
    // Flatten (same as .flatten())
    let flat: Vec<_> = nested.iter().flatten().copied().collect();
    println!("Flattened: {:?}", flat);
    
    // Flatten one level
    let flat2: Vec<_> = nested.iter().concat();
    println!("Concatenated: {:?}", flat2);
    
    // Flatten Option
    let options = vec![Some(1), None, Some(2), None, Some(3)];
    let flattened: Vec<_> = options.into_iter().flatten().collect();
    println!("Flattened Options: {:?}", flattened);
    
    // Flatten Result
    let results: Vec<Result<i32, &str>> = vec![Ok(1), Err("error"), Ok(2)];
    let ok_values: Vec<_> = results.into_iter().filter_map(|r| r.ok()).collect();
    println!("Ok values: {:?}", ok_values);
}

Min, Max, and Extremes

use itertools::Itertools;
 
fn main() {
    let numbers = vec![3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5];
    
    // Min and max
    println!("Min: {:?}", numbers.iter().min());
    println!("Max: {:?}", numbers.iter().max());
    
    // Min/Max by key
    let items = vec![("a", 3), ("b", 1), ("c", 4), ("d", 1)];
    let min_by_val = items.iter().min_by_key(|(_, v)| v);
    println!("Min by value: {:?}", min_by_val);
    
    // Min/Max position
    let min_pos = numbers.iter().position_min();
    let max_pos = numbers.iter().position_max();
    println!("Min position: {:?}, Max position: {:?}", min_pos, max_pos);
    
    // Multiple min/max
    let minmax = numbers.iter().minmax();
    println!("Min/Max: {:?}", minmax);
    
    // All extremes
    let maxes: Vec<_> = items.iter().max_set_by_key(|(_, v)| *v);
    println!("All max items: {:?}", maxes);
}

Partitioning

use itertools::Itertools;
 
fn main() {
    let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    
    // Partition into two collections
    let (evens, odds): (Vec<_>, Vec<_>) = numbers.iter()
        .copied()
        .partition(|&x| x % 2 == 0);
    
    println!("Evens: {:?}", evens);
    println!("Odds: {:?}", odds);
    
    // Partition in place
    let mut numbers2 = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    let split_idx = itertools::partition(&mut numbers2, |&x| x % 2 == 0);
    println!("Partitioned: {:?}", numbers2);
    println!("Split at index: {}", split_idx);
}

Diffing Sequences

use itertools::Itertools;
 
fn main() {
    let old = vec![1, 2, 3, 4, 5];
    let new = vec![1, 2, 4, 5, 6];
    
    // Compare sequences
    for diff in old.iter().diff(new.iter()) {
        println!("Diff: {:?}", diff);
    }
    
    // Compare with positions
    let old2 = vec!['a', 'b', 'c', 'd'];
    let new2 = vec!['a', 'b', 'x', 'd'];
    
    for change in old2.iter().diff_with(new2.iter()) {
        match change {
            itertools::Diff::FirstMismatch(pos, old, new) => {
                println!("Mismatch at {}: {:?} vs {:?}", pos, old, new);
            }
            _ => {}
        }
    }
    
    // Longest common prefix
    let common_len = old.iter().zip(new.iter()).take_while(|(a, b)| a == b).count();
    println!("Common prefix length: {}", common_len);
}

Iterating Manually

use itertools::Itertools;
 
fn main() {
    let numbers = vec![1, 2, 3, 4, 5];
    
    // Next or default
    let mut iter = numbers.iter().copied();
    println!("Next: {:?}", iter.next());
    println!("Next or 0: {:?}", iter.next_or(0));
    
    // Iterate with done flag
    for (done, val) in numbers.iter().with_done() {
        if done {
            println!("Last: {}", val);
        } else {
            println!("Not last: {}", val);
        }
    }
    
    // Cycle forever
    let cycled: Vec<_> = numbers.iter().cycle().take(10).copied().collect();
    println!("Cycled (10): {:?}", cycled);
}

Real-World: Processing CSV Data

use itertools::Itertools;
 
struct Record {
    id: u32,
    category: String,
    value: f64,
}
 
fn main() {
    let records = vec![
        Record { id: 1, category: "A".to_string(), value: 10.0 },
        Record { id: 2, category: "B".to_string(), value: 20.0 },
        Record { id: 3, category: "A".to_string(), value: 15.0 },
        Record { id: 4, category: "B".to_string(), value: 25.0 },
        Record { id: 5, category: "A".to_string(), value: 12.0 },
    ];
    
    // Group by category and compute statistics
    let stats: Vec<_> = records.iter()
        .group_by(|r| r.category.as_str())
        .into_iter()
        .map(|(category, group)| {
            let values: Vec<_> = group.map(|r| r.value).collect();
            let sum: f64 = values.iter().sum();
            let count = values.len();
            let avg = sum / count as f64;
            (category, count, sum, avg)
        })
        .collect();
    
    println!("Category Statistics:");
    for (cat, count, sum, avg) in stats {
        println!("  {}: count={}, sum={:.1}, avg={:.1}", cat, count, sum, avg);
    }
}

Real-World: Finding Anagrams

use itertools::Itertools;
use std::collections::HashMap;
 
fn main() {
    let words = vec!["listen", "silent", "enlist", "hello", "world", "dlrow", "evil", "live", "vile"];
    
    // Group words by their sorted character multiset
    let anagrams: HashMap<Vec<char>, Vec<&str>> = words.iter()
        .map(|&word| {
            let mut chars: Vec<char> = word.chars().collect();
            chars.sort();
            (chars, word)
        })
        .into_group_map();
    
    println!("Anagrams:");
    for (_, group) in anagrams.iter().filter(|(_, g)| g.len() > 1) {
        println!("  {:?}", group);
    }
}

Real-World: Generating Test Cases

use itertools::Itertools;
 
fn main() {
    // Generate all possible test inputs
    let operations = vec!["add", "remove", "query"];
    let keys = vec!["user_1", "user_2", "user_3"];
    let values = vec![100, 200, 300];
    
    let test_cases: Vec<_> = operations.iter()
        .cartesian_product(keys.iter())
        .cartesian_product(values.iter())
        .map(|((op, key), value)| format!("{}({}, {})", op, key, value))
        .collect();
    
    println!("Test cases:");
    for tc in test_cases.iter().take(10) {
        println!("  {}", tc);
    }
    println!("... ({} total)", test_cases.len());
}

Real-World: Sliding Window Processing

use itertools::Itertools;
 
fn main() {
    let prices = vec![100.0, 102.0, 101.0, 105.0, 103.0, 107.0, 106.0, 110.0];
    
    // Calculate moving average
    println!("Moving averages (window=3):");
    for (i, window) in prices.iter().tuple_windows::<(_,_,_)>().enumerate() {
        let (a, b, c) = window;
        let avg = (a + b + c) / 3.0;
        println!("  Day {}-{}: avg = {:.2}", i + 1, i + 3, avg);
    }
    
    // Detect trends
    println!("\nTrend changes:");
    for ((prev, curr), next) in prices.iter().tuple_windows::<(_,_,_)>() {
        if prev < curr && curr > next {
            println!("  Peak at {:.2}", curr);
        } else if prev > curr && curr < next {
            println!("  Valley at {:.2}", curr);
        }
    }
}

Real-World: Comparing Versions

use itertools::Itertools;
 
fn parse_version(version: &str) -> Vec<u32> {
    version.split('.').filter_map(|s| s.parse().ok()).collect()
}
 
fn main() {
    let versions = vec!["1.0.0", "1.2.0", "1.2.1", "2.0.0", "2.0.1"];
    
    // Find version between two versions
    let target = "1.2.0";
    let target_ver = parse_version(target);
    
    let (before, after): (Vec<_>, Vec<_>) = versions.iter()
        .partition(|v| {
            let v_parsed = parse_version(v);
            v_parsed < target_ver
        });
    
    println!("Before {}: {:?}", target, before);
    println!("After or equal {}: {:?}", target, after);
}

Real-World: Combining Multiple Data Sources

use itertools::Itertools;
 
struct User {
    id: u32,
    name: String,
}
 
struct Order {
    user_id: u32,
    product: String,
    amount: f64,
}
 
fn main() {
    let users = vec![
        User { id: 1, name: "Alice".to_string() },
        User { id: 2, name: "Bob".to_string() },
        User { id: 3, name: "Charlie".to_string() },
    ];
    
    let orders = vec![
        Order { user_id: 1, product: "Book".to_string(), amount: 25.0 },
        Order { user_id: 2, product: "Laptop".to_string(), amount: 1000.0 },
        Order { user_id: 1, product: "Pen".to_string(), amount: 5.0 },
        Order { user_id: 3, product: "Notebook".to_string(), amount: 15.0 },
    ];
    
    // Group orders by user
    let orders_by_user = orders.iter()
        .group_by(|o| o.user_id)
        .into_iter()
        .map(|(user_id, group)| {
            (user_id, group.collect::<Vec<_>>())
        })
        .collect::<std::collections::HashMap<_, _>>();
    
    // Print user order summary
    for user in users {
        if let Some(user_orders) = orders_by_user.get(&user.id) {
            let total: f64 = user_orders.iter().map(|o| o.amount).sum();
            println!("{}: {} orders, ${:.2} total", 
                     user.name, user_orders.len(), total);
        }
    }
}

Real-World: Building a Parser

use itertools::Itertools;
 
#[derive(Debug, Clone)]
enum Token {
    Number(i32),
    Plus,
    Minus,
    Multiply,
    Divide,
}
 
fn tokenize(input: &str) -> Vec<Token> {
    input.chars()
        .filter(|c| !c.is_whitespace())
        .batching(|it| {
            let c = it.next()?;
            Some(match c {
                '+' => Token::Plus,
                '-' => Token::Minus,
                '*' => Token::Multiply,
                '/' => Token::Divide,
                n if n.is_ascii_digit() => {
                    let mut num = n.to_string();
                    while let Some(&c) = it.peek() {
                        if c.is_ascii_digit() {
                            num.push(it.next()?);
                        } else {
                            break;
                        }
                    }
                    Token::Number(num.parse().unwrap())
                }
                _ => return None,
            })
        })
        .collect()
}
 
fn main() {
    let input = "12 + 34 * 2 - 5";
    let tokens = tokenize(input);
    println!("Tokens: {:?}", tokens);
}

Real-World: Deduplicating Log Lines

use itertools::Itertools;
 
#[derive(Debug, Clone)]
struct LogLine {
    timestamp: String,
    level: String,
    message: String,
}
 
fn main() {
    let logs = vec![
        LogLine { timestamp: "2024-01-01T10:00:00".into(), level: "INFO".into(), message: "Started".into() },
        LogLine { timestamp: "2024-01-01T10:00:01".into(), level: "INFO".into(), message: "Processing".into() },
        LogLine { timestamp: "2024-01-01T10:00:02".into(), level: "INFO".into(), message: "Processing".into() },
        LogLine { timestamp: "2024-01-01T10:00:03".into(), level: "WARN".into(), message: "Slow query".into() },
        LogLine { timestamp: "2024-01-01T10:00:04".into(), level: "INFO".into(), message: "Processing".into() },
        LogLine { timestamp: "2024-01-01T10:00:05".into(), level: "ERROR".into(), message: "Failed".into() },
    ];
    
    // Dedupe consecutive identical messages
    let deduped: Vec<_> = logs.iter()
        .coalesce(|a, b| {
            if a.message == b.message {
                Ok(a.clone())
            } else {
                Err((a.clone(), b.clone()))
            }
        })
        .collect();
    
    println!("Deduplicated logs:");
    for log in deduped {
        println!("  [{}] {} - {}", log.timestamp, log.level, log.message);
    }
}

Performance Tips

use itertools::Itertools;
 
fn main() {
    let large: Vec<i32> = (0..1000000).collect();
    
    // Use specialized methods when available
    // concat is faster than flatten + collect
    let nested: Vec<Vec<i32>> = vec![vec![1, 2], vec![3, 4]];
    let flat: Vec<i32> = nested.into_iter().concat();
    
    // Use fold_while for early termination
    let result = (0..).fold_while(0, |acc, x| {
        if acc > 100 {
            itertools::FoldWhile::Done(acc)
        } else {
            itertools::FoldWhile::Continue(acc + x)
        }
    });
    
    println!("Fold while result: {:?}", result.into_inner());
    
    // Avoid collect when not needed
    let sum: i32 = large.iter()
        .copied()
        .fold(0, |acc, x| acc + x);
    
    println!("Sum computed without collecting: {}", sum);
}

Summary

  • Add use itertools::Itertools; to access all extension methods
  • unique() and unique_by() remove duplicates while preserving order
  • combinations(n) generates all n-element combinations
  • permutations(n) generates all n-element permutations
  • cartesian_product() combines every element from two iterators
  • interleave() alternates between two iterators
  • group_by() groups consecutive elements by a key
  • chunks(n) splits into fixed-size chunks
  • merge() combines sorted iterators into one sorted iterator
  • partition() splits elements into two collections
  • tuple_windows() creates sliding windows of tuples
  • minmax() finds both min and max in one pass
  • collect_tuple() collects exactly N elements into a tuple
  • join() concatenates elements with a separator
  • fold_while() allows early termination in a fold
  • with_position() identifies first/middle/last elements
  • coalesce() merges consecutive elements
  • Perfect for: data pipelines, combinatorics, parsing, grouping, statistics, algorithm implementation