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
windowson an iterator:
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).
zipsilently drops leftovers. If the two sides have different lengths, the extra items are ignored. When that would be a bug, check the lengths first.- Forgetting
enumeratestarts at 0. Add 1 for numbers shown to people. - Filtering before
enumeratewhen you want the original positions.enumeratenumbers whatever reaches it. Put it first to number the original items.
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