Step 1 of 5 · Reading · ~4 min
Learn
Collections
Iterators
An iterator hands you the items of a sequence one at a time. By the end of this lesson you can chain iterator steps together to keep some items, transform them, and collapse them into a single answer, without writing a for loop or a mutable accumulator.
Making an iterator and consuming it
.iter() on a Vec gives an iterator that borrows each element in turn, just like for x in &v did in the Vec lesson. On its own it does nothing. A consumer is a method that pulls every item through and produces a result:
let scores = vec![4, 9, 2, 7];
let n = scores.iter().count(); // 4
let total: i64 = scores.iter().sum(); // 22
sum() can build several number types, so you name the one you want: let total: i64 = ... (or .sum::<i64>()). An empty sum is 0, so an iterator with no items gives 0, not an error.
Closures
A closure is a small unnamed function written inline. Parameters go between pipes, the body follows:
let add_tax = |price: i64| price + price / 10;
println!("{}", add_tax(200)); // 220
Iterator steps take closures to say what to do with each item.
Adapters: map and filter
An adapter takes an iterator and returns a new one. It does no work until a consumer runs the chain; iterators are lazy. If you write an adapter chain with no consumer, the compiler warns that iterators "do nothing unless consumed".
.map(f)replaces each item withf(item)..filter(f)keeps an item only whenf(item)istrue.
Here is a different problem: temperatures above 20 count as heat, and you want the total degrees of heat.
let temps = vec![18, 23, 20, 27, 15];
let heat: i64 = temps.iter()
.filter(|t| **t > 20)
.map(|t| t - 20)
.sum();
println!("{}", heat); // 10
Trace it item by item:
| item | filter > 20 | map - 20 | running sum |
|---|---|---|---|
| 18 | dropped | 0 | |
| 23 | kept | 3 | 3 |
| 20 | dropped (not greater) | 3 | |
| 27 | kept | 7 | 10 |
| 15 | dropped | 10 |
Why **t? .iter() yields &i64, and filter passes its closure a reference to each item, so t is &&i64. Two stars reach the number. In map, t is a plain &i64, and arithmetic on a reference works directly, producing an i64.
Order matters: filter sees the value before map changes it. Swap the two and you would be testing t - 20 > 20 instead.
The remainder trap with negatives
% gives the remainder, and in Rust its sign follows the left side:
| expression | result |
|---|---|
7 % 2 | 1 |
-7 % 2 | -1 |
-6 % 2 | 0 |
9 % 3 | 0 |
-9 % 3 | 0 |
A remainder of 0 means "divides evenly" for negative numbers too. But a test like x % 2 == 1 is wrong for negatives: -7 % 2 is -1, so -7 is not recognised as odd. When you mean "not divisible", compare against zero with != 0.
Common mistakes
- Testing oddness with
== 1and negating it. Negative odd numbers give a remainder of-1, so they slip through as even. For-4 -3 2that prints29instead of20. - Only doing half the pipeline. Adding the evens without squaring (
8 1gives8, not64), or squaring everything without filtering (1 2 3 4 5gives55, not20). - Filtering by position instead of value. Keeping the items at even positions (0, 2, 4, ...) is a different question:
1 2 3 4 5then gives35. - Using
count()where you needsum().2 4 6prints3, the number of items kept, instead of their total.
Your exercise
Print the sum of the squares of the even numbers on one line of input. The starter already parses the line into nums: Vec<i64> and prints total; you replace the 0 with an iterator chain. Watch the negative numbers: an even test that only works for positives will fail.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…