BBuzzard

Adapters take an iterator and return a new, lazy one: map, filter, filter_map, take, skip, take_while, enumerate, zip, chain, flatten, flat_map, peekable, inspect, rev, step_by, cycle, cloned, copied. Consumers pull the values through and produce a result: collect, sum, product, count, fold, reduce, min, max, any, all, find, position, for_each.

The usual shape is: describe the work as a chain of adapters, then consume once. collect is driven by the type you ask for: Vec, String, HashMap, HashSet, and even Result<Vec<_>, E>, which stops at the first error.

Map, filter, and friends

fn demo_map_filter() {
    println!("-- map / filter --");

    let v: Vec<_> = (0..10).filter(|n| n % 2 == 0).map(|n| n * n).take(3).collect();
    println!("{v:?}");

    let words = ["3", "x", "7", "", "10"];
    let nums: Vec<i32> = words.iter().filter_map(|w| w.parse().ok()).collect();
    println!("filter_map {nums:?}");

    let firsts: Vec<char> =
        ["apple", "", "kiwi"].iter().filter_map(|w| w.chars().next()).collect();
    println!("first chars {firsts:?}");
    println!("skip/step_by {:?}", (0..20).skip(5).step_by(4).collect::<Vec<_>>());
}

take_while, skip_while, map_while

fn demo_while_adapters() {
    println!("-- take_while / skip_while --");

    let v = [1, 3, 5, 6, 7, 9];
    println!(
        "take_while odd {:?}",
        v.iter().take_while(|n| *n % 2 == 1).collect::<Vec<_>>()
    );
    println!(
        "skip_while odd {:?}",
        v.iter().skip_while(|n| *n % 2 == 1).collect::<Vec<_>>()
    );

    let parsed: Vec<i32> =
        ["1", "2", "x", "4"].iter().map_while(|s| s.parse().ok()).collect();
    println!("map_while stops at first failure {parsed:?}");
}

Enumerate, zip, unzip, chain, cycle

fn demo_combining() {
    println!("-- combining --");

    for (i, c) in "abc".chars().enumerate() {
        print!("{i}{c} ");
    }

    println!();
    let names = ["ann", "ben", "cy"];
    let ages = [30, 25];
    let pairs: Vec<_> = names.iter().zip(ages.iter()).collect();
    println!("zip stops at the shorter: {pairs:?}");

    let (left, right): (Vec<_>, Vec<_>) = [(1, 'a'), (2, 'b')].into_iter().unzip();
    println!("unzip {left:?} {right:?}");

    let all: Vec<_> = (1..3).chain(7..9).collect();
    println!("chain {all:?}");

    let teams = ["red", "blue"];
    let assigned: Vec<_> = names.iter().zip(teams.iter().cycle()).collect();
    println!("cycle {assigned:?}");
}

Flatten and flat_map

fn demo_flatten() {
    println!("-- flatten / flat_map --");

    let nested = vec![vec![1, 2], vec![], vec![3]];
    println!("flatten {:?}", nested.into_iter().flatten().collect::<Vec<_>>());

    let lines = ["a b", "c d e"];
    let words: Vec<&str> = lines.iter().flat_map(|l| l.split(' ')).collect();
    println!("flat_map {words:?}");

    let opts = [Some(1), None, Some(3)];
    println!("flatten Options {:?}", opts.iter().flatten().collect::<Vec<_>>());

    let doubled: Vec<_> = [1, 2, 3].iter().copied().flat_map(|n| [n, n]).collect();
    println!("{doubled:?}");
}

Peekable, inspect, cloned, copied

fn demo_peek_inspect() {
    println!("-- peekable / inspect --");

    let mut it = [1, 2, 3].into_iter().peekable();
    println!("peek {:?}", it.peek());
    println!("next {:?}", it.next());
    println!("peek {:?}", it.peek());

    // A tiny tokenizer: group runs of digits.
    let mut chars = "12+345-6".chars().peekable();
    let mut tokens = Vec::new();
    while let Some(c) = chars.next() {
        if c.is_ascii_digit() {
            let mut num = c.to_string();
            while let Some(d) = chars.next_if(|d| d.is_ascii_digit()) {
                num.push(d);
            }

            tokens.push(num);
        } else {
            tokens.push(c.to_string());
        }
    }

    println!("tokens {tokens:?}");

    let v: Vec<i32> = [1, 2, 3]
        .iter()
        .inspect(|n| print!("see {n} "))
        .copied()
        .filter(|n| n % 2 == 1)
        .collect();

    println!("-> {v:?}");
    let owned: Vec<String> = [String::from("a")].iter().cloned().collect();
    println!("cloned {owned:?}");
}

Fold, reduce, scan, and the numeric consumers

fn demo_fold() {
    println!("-- fold / reduce / scan --");

    let sum = (1..=4).fold(0, |acc, n| acc + n);
    let prod = (1..=4).product::<i32>();
    let max = [3, 9, 1].into_iter().reduce(|a, b| a.max(b));
    println!("{sum} {prod} {max:?}");

    let csv = ["a", "b", "c"].iter().fold(String::new(), |mut acc, s| {
        if !acc.is_empty() {
            acc.push(',');
        }

        acc.push_str(s);
        acc
    });

    println!("fold into string {csv}");
    let running: Vec<_> = [1, 2, 3, 4]
        .iter()
        .scan(0, |acc, &n| {
            *acc += n;
            Some(*acc)
        })
        .collect();

    println!("running totals {running:?}");
    let checked = [100u8, 100, 100].iter().try_fold(0u8, |acc, &n| acc.checked_add(n));
    println!("try_fold with overflow {checked:?}");
}

Searching and testing

fn demo_search() {
    println!("-- search --");

    let v = [4, 8, 15, 16, 23, 42];
    println!(
        "any >40 {} all even {}",
        v.iter().any(|&n| n > 40),
        v.iter().all(|n| n % 2 == 0)
    );
    println!(
        "find {:?} position {:?}",
        v.iter().find(|&&n| n > 10),
        v.iter().position(|&n| n == 16)
    );
    println!(
        "find_map {:?}",
        ["x", "5", "y"].iter().find_map(|s| s.parse::<i32>().ok())
    );
    println!(
        "min {:?} max_by_key {:?}",
        v.iter().min(),
        ["aa", "b", "cccc"].iter().max_by_key(|s| s.len())
    );

    let (even, odd): (Vec<i32>, Vec<i32>) = v.iter().partition(|&&n| n % 2 == 0);
    println!("partition {even:?} {odd:?}");
}

Collecting into different targets

fn demo_collect() {
    println!("-- collect --");

    let set: HashSet<_> = ["a", "a", "b"].into_iter().collect();
    println!("HashSet len {}", set.len());

    let s: String = ['r', 'u', 's', 't'].into_iter().collect();
    println!("String {s}");

    let joined: String =
        ["x", "y"].iter().map(|s| s.to_uppercase()).collect::<Vec<_>>().join("-");
    println!("join {joined}");

    let r: Result<Vec<_>, _> =
        ["1", "2", "x"].iter().map(|t| t.parse::<i32>()).collect();
    println!("Result {r:?}");

    // Pairs collect into maps; with duplicate keys, the later one wins.
    let m: HashMap<_, _> = [("a", 1), ("b", 2), ("a", 3)].into_iter().collect();
    let mut keys: Vec<_> = m.iter().collect();
    keys.sort();
    println!("HashMap {keys:?}");

    let lens: BTreeMap<&str, usize> =
        ["pear", "fig"].iter().map(|w| (*w, w.len())).collect();
    println!("BTreeMap {lens:?}");

    let (keys, vals): (Vec<_>, Vec<_>) = lens.into_iter().unzip();
    println!("unzip a map {keys:?} {vals:?}");
}
$ cargo run --bin 31_iterator_adapters
-- map / filter --
[0, 4, 16]
filter_map [3, 7, 10]
first chars ['a', 'k']
skip/step_by [5, 9, 13, 17]
-- take_while / skip_while --
take_while odd [1, 3, 5]
skip_while odd [6, 7, 9]
map_while stops at first failure [1, 2]
-- combining --
0a 1b 2c 
zip stops at the shorter: [("ann", 30), ("ben", 25)]
unzip [1, 2] ['a', 'b']
chain [1, 2, 7, 8]
cycle [("ann", "red"), ("ben", "blue"), ("cy", "red")]
-- flatten / flat_map --
flatten [1, 2, 3]
flat_map ["a", "b", "c", "d", "e"]
flatten Options [1, 3]
[1, 1, 2, 2, 3, 3]
-- peekable / inspect --
peek Some(1)
next Some(1)
peek Some(2)
tokens ["12", "+", "345", "-", "6"]
see 1 see 2 see 3 -> [1, 3]
cloned ["a"]
-- fold / reduce / scan --
10 24 Some(9)
fold into string a,b,c
running totals [1, 3, 6, 10]
try_fold with overflow None
-- search --
any >40 true all even false
find Some(15) position Some(3)
find_map Some(5)
min Some(4) max_by_key Some("cccc")
partition [4, 8, 16, 42] [15, 23]
-- collect --
HashSet len 2
String rust
join X-Y
Result Err(ParseIntError { kind: InvalidDigit })
HashMap [("a", 3), ("b", 2)]
BTreeMap {"fig": 3, "pear": 4}
unzip a map ["fig", "pear"] [3, 4]

Full program

31_iterator_adapters/main.rs · 237 lines
View on GitHub
// 31. Iterator adapters and consumers
//
// Adapters take an iterator and return a new, lazy one: map, filter, filter_map, take,
// skip, take_while, enumerate, zip, chain, flatten, flat_map, peekable, inspect, rev,
// step_by, cycle, cloned, copied. Consumers pull the values through and produce a
// result: collect, sum, product, count, fold, reduce, min, max, any, all, find,
// position, for_each.
//
// The usual shape is: describe the work as a chain of adapters, then consume once.
// collect is driven by the type you ask for: Vec, String, HashMap, HashSet, and even
// Result<Vec<_>, E>, which stops at the first error.
//
// Run: cargo run --bin 31_iterator_adapters

use std::collections::{BTreeMap, HashMap, HashSet};

// Example 1: map, filter, and friends.
fn demo_map_filter() {
    println!("-- map / filter --");

    let v: Vec<_> = (0..10).filter(|n| n % 2 == 0).map(|n| n * n).take(3).collect();
    println!("{v:?}");

    let words = ["3", "x", "7", "", "10"];
    let nums: Vec<i32> = words.iter().filter_map(|w| w.parse().ok()).collect();
    println!("filter_map {nums:?}");

    let firsts: Vec<char> =
        ["apple", "", "kiwi"].iter().filter_map(|w| w.chars().next()).collect();
    println!("first chars {firsts:?}");
    println!("skip/step_by {:?}", (0..20).skip(5).step_by(4).collect::<Vec<_>>());
}

// Example 2: take_while, skip_while, map_while.
fn demo_while_adapters() {
    println!("-- take_while / skip_while --");

    let v = [1, 3, 5, 6, 7, 9];
    println!(
        "take_while odd {:?}",
        v.iter().take_while(|n| *n % 2 == 1).collect::<Vec<_>>()
    );
    println!(
        "skip_while odd {:?}",
        v.iter().skip_while(|n| *n % 2 == 1).collect::<Vec<_>>()
    );

    let parsed: Vec<i32> =
        ["1", "2", "x", "4"].iter().map_while(|s| s.parse().ok()).collect();
    println!("map_while stops at first failure {parsed:?}");
}

// Example 3: enumerate, zip, unzip, chain, cycle.
fn demo_combining() {
    println!("-- combining --");

    for (i, c) in "abc".chars().enumerate() {
        print!("{i}{c} ");
    }

    println!();
    let names = ["ann", "ben", "cy"];
    let ages = [30, 25];
    let pairs: Vec<_> = names.iter().zip(ages.iter()).collect();
    println!("zip stops at the shorter: {pairs:?}");

    let (left, right): (Vec<_>, Vec<_>) = [(1, 'a'), (2, 'b')].into_iter().unzip();
    println!("unzip {left:?} {right:?}");

    let all: Vec<_> = (1..3).chain(7..9).collect();
    println!("chain {all:?}");

    let teams = ["red", "blue"];
    let assigned: Vec<_> = names.iter().zip(teams.iter().cycle()).collect();
    println!("cycle {assigned:?}");
}

// Example 4: flatten and flat_map.
fn demo_flatten() {
    println!("-- flatten / flat_map --");

    let nested = vec![vec![1, 2], vec![], vec![3]];
    println!("flatten {:?}", nested.into_iter().flatten().collect::<Vec<_>>());

    let lines = ["a b", "c d e"];
    let words: Vec<&str> = lines.iter().flat_map(|l| l.split(' ')).collect();
    println!("flat_map {words:?}");

    let opts = [Some(1), None, Some(3)];
    println!("flatten Options {:?}", opts.iter().flatten().collect::<Vec<_>>());

    let doubled: Vec<_> = [1, 2, 3].iter().copied().flat_map(|n| [n, n]).collect();
    println!("{doubled:?}");
}

// Example 5: peekable, inspect, cloned, copied.
fn demo_peek_inspect() {
    println!("-- peekable / inspect --");

    let mut it = [1, 2, 3].into_iter().peekable();
    println!("peek {:?}", it.peek());
    println!("next {:?}", it.next());
    println!("peek {:?}", it.peek());

    // A tiny tokenizer: group runs of digits.
    let mut chars = "12+345-6".chars().peekable();
    let mut tokens = Vec::new();
    while let Some(c) = chars.next() {
        if c.is_ascii_digit() {
            let mut num = c.to_string();
            while let Some(d) = chars.next_if(|d| d.is_ascii_digit()) {
                num.push(d);
            }

            tokens.push(num);
        } else {
            tokens.push(c.to_string());
        }
    }

    println!("tokens {tokens:?}");

    let v: Vec<i32> = [1, 2, 3]
        .iter()
        .inspect(|n| print!("see {n} "))
        .copied()
        .filter(|n| n % 2 == 1)
        .collect();

    println!("-> {v:?}");
    let owned: Vec<String> = [String::from("a")].iter().cloned().collect();
    println!("cloned {owned:?}");
}

// Example 6: fold, reduce, scan, and the numeric consumers.
fn demo_fold() {
    println!("-- fold / reduce / scan --");

    let sum = (1..=4).fold(0, |acc, n| acc + n);
    let prod = (1..=4).product::<i32>();
    let max = [3, 9, 1].into_iter().reduce(|a, b| a.max(b));
    println!("{sum} {prod} {max:?}");

    let csv = ["a", "b", "c"].iter().fold(String::new(), |mut acc, s| {
        if !acc.is_empty() {
            acc.push(',');
        }

        acc.push_str(s);
        acc
    });

    println!("fold into string {csv}");
    let running: Vec<_> = [1, 2, 3, 4]
        .iter()
        .scan(0, |acc, &n| {
            *acc += n;
            Some(*acc)
        })
        .collect();

    println!("running totals {running:?}");
    let checked = [100u8, 100, 100].iter().try_fold(0u8, |acc, &n| acc.checked_add(n));
    println!("try_fold with overflow {checked:?}");
}

// Example 7: searching and testing.
fn demo_search() {
    println!("-- search --");

    let v = [4, 8, 15, 16, 23, 42];
    println!(
        "any >40 {} all even {}",
        v.iter().any(|&n| n > 40),
        v.iter().all(|n| n % 2 == 0)
    );
    println!(
        "find {:?} position {:?}",
        v.iter().find(|&&n| n > 10),
        v.iter().position(|&n| n == 16)
    );
    println!(
        "find_map {:?}",
        ["x", "5", "y"].iter().find_map(|s| s.parse::<i32>().ok())
    );
    println!(
        "min {:?} max_by_key {:?}",
        v.iter().min(),
        ["aa", "b", "cccc"].iter().max_by_key(|s| s.len())
    );

    let (even, odd): (Vec<i32>, Vec<i32>) = v.iter().partition(|&&n| n % 2 == 0);
    println!("partition {even:?} {odd:?}");
}

// Example 8: collecting into different targets.
fn demo_collect() {
    println!("-- collect --");

    let set: HashSet<_> = ["a", "a", "b"].into_iter().collect();
    println!("HashSet len {}", set.len());

    let s: String = ['r', 'u', 's', 't'].into_iter().collect();
    println!("String {s}");

    let joined: String =
        ["x", "y"].iter().map(|s| s.to_uppercase()).collect::<Vec<_>>().join("-");
    println!("join {joined}");

    let r: Result<Vec<_>, _> =
        ["1", "2", "x"].iter().map(|t| t.parse::<i32>()).collect();
    println!("Result {r:?}");

    // Pairs collect into maps; with duplicate keys, the later one wins.
    let m: HashMap<_, _> = [("a", 1), ("b", 2), ("a", 3)].into_iter().collect();
    let mut keys: Vec<_> = m.iter().collect();
    keys.sort();
    println!("HashMap {keys:?}");

    let lens: BTreeMap<&str, usize> =
        ["pear", "fig"].iter().map(|w| (*w, w.len())).collect();
    println!("BTreeMap {lens:?}");

    let (keys, vals): (Vec<_>, Vec<_>) = lens.into_iter().unzip();
    println!("unzip a map {keys:?} {vals:?}");
}

fn main() {
    demo_map_filter();
    demo_while_adapters();
    demo_combining();
    demo_flatten();
    demo_peek_inspect();
    demo_fold();
    demo_search();
    demo_collect();
}