RustArena

Step 4 of 7

Combining iterators: enumerate, zip, chain, flat_map, windows

The real power of iterators is in combining them. These adapters cover most of what loops with index variables used to do.

fn main() {
    let players = ["Ann", "Bo", "Cy"];
    let scores = [88, 92, 75];

    // enumerate: (index, item) pairs, starting at 0
    for (i, name) in players.iter().enumerate() {
        println!("{}. {name}", i + 1);
    }

    // zip: walk two iterators side by side, as pairs
    let report: Vec<String> = players
        .iter()
        .zip(scores.iter())
        .map(|(name, score)| format!("{name}={score}"))
        .collect();
    println!("{}", report.join(", "));

    // chain: one iterator after the other
    let morning = [1, 2];
    let evening = [3, 4];
    let all: Vec<i32> = morning.iter().chain(evening.iter()).copied().collect();
    println!("{:?}", all);
}
1. Ann
2. Bo
3. Cy
Ann=88, Bo=92, Cy=75
[1, 2, 3, 4]

Notice .map(|(name, score)| ...): zip yields tuples, and the closure's parameter is a pattern that takes the tuple apart, just like let (a, b) = pair;.

fn main() {
    // flat_map: each item becomes an iterator; all of them are joined
    let lines = ["error disk full", "warn cpu hot"];
    let words: Vec<&str> = lines.iter().flat_map(|l| l.split_whitespace()).collect();
    println!("{:?}", words);

    // flatten: removes one level of nesting (and skips the Nones in Options)
    let nested = vec![vec![1, 2], vec![], vec![3]];
    let flat: Vec<i32> = nested.into_iter().flatten().collect();
    println!("{:?}", flat);

    // windows: overlapping slices; chunks: side-by-side slices
    let temps = [12, 15, 14, 18, 21];
    let rises = temps.windows(2).filter(|w| w[1] > w[0]).count();
    println!("rises: {rises}");
    let batches: Vec<Vec<i32>> = temps.chunks(2).map(|c| c.to_vec()).collect();
    println!("{:?}", batches);

    // peekable: look at the next item without taking it
    let rolls = [1, 1, 2, 2, 2, 3];
    let mut it = rolls.iter().peekable();
    while let Some(x) = it.next() {
        let mut run = 1;
        while it.peek() == Some(&x) {
            it.next();
            run += 1;
        }
        print!("{x}x{run} ");
    }
    println!();
}
["error", "disk", "full", "warn", "cpu", "hot"]
[1, 2, 3]
rises: 3
[[12, 15], [14, 18], [21]]
1x2 2x3 3x1
Adapter Yields Typical use
enumerate() (index, item), index from 0 numbering lines, finding positions
zip(other) (a, b) pairs; stops when either side runs out pairing names with scores
chain(other) all items of the first, then all of the second joining two sources
flat_map(f) for each item, every item of the iterator f returns all words of all lines
flatten() the items inside each item Vec<Vec<T>> to Ts, dropping Nones
peekable() the same items, plus a .peek() method parsers, grouping runs
windows(n) every overlapping slice of length n comparing neighbors
chunks(n) slices of n items side by side (the last may be shorter) batching

windows and chunks are methods of slices, not adapters: call them on a Vec or array, not on an iterator. They return iterators, so the rest of the chain works as usual.

Common mistakes

fn main() {
    let v = vec![1, 2, 3];
    let n = v.iter().windows(2).count();
    println!("{n}");
}
error[E0599]: no method named `windows` found for struct `std::slice::Iter<'a, T>` in the current scope

Write v.windows(2).

Your task

Write fn number_lines(lines: &[&str]) -> Vec<String> that returns "N: line" for every line that isn't blank, where N is the line's number in the original list (starting at 1). Blank lines (empty, or only spaces) are skipped but still count for the numbering. For example ["fn main() {", "", "}"] gives ["1: fn main() {", "3: }"]. No loops.

Challenge 2

Write fn dot_product(a: &[i32], b: &[i32]) -> i32: multiply the numbers at the same positions and add the products up. dot_product(&[1, 2, 3], &[4, 5, 6]) is 1*4 + 2*5 + 3*6 = 32. If the lengths differ, ignore the extra numbers of the longer one (that's what zip does). No loops.

Challenge 3

Write fn unique_words(lines: &[&str]) -> Vec<String> that returns every word that appears in any of the lines, lowercased, sorted alphabetically, with no duplicates. Words are separated by whitespace. Use flat_map to get from lines to words; no loops.

Previous: Laziness: nothing happens until you ask Next: Consumers: sum, count, min, max, fold, any, find