11 Iterators
- Understand what an iterator is and how to create one.
- Learn the difference between
.iter()
and.into_iter()
. - Use basic iterator methods like
.sum()
,.min()
,.max()
, and.enumerate()
.
So far we have been using a for
loop to iterate through our items. However, it is not the only way to iterate. Instead, we can use iterators. Iterators often are simpler and easier to use—particularly when modifying each element in a Vec<T>
.
Iterators can be created from basic collections like vectors or arrays as well as more advanced types such as HashMap
s. They produce a sequence of items that can be accessed (or modified) one at a time.
Consuming vs. Borrowing
When we use a for loop with a collection, we are consuming (moving) the underlying vector. Under the hood, using a for loop actually creates an iterator by calling the .into_iter()
method.
for xi in x
is identical to calling for xi in x.into_iter()
for example:
fn main() {
let nums = vec![3, 6, 9];
for n in nums.into_iter() {
println!("Value: {n}");
}
// nums has been moved ❌
// println!("nums: {:?}", nums);
}
Remember: .into_iter()
consumes the original value.
Alternatively, the .iter()
method can be called which borrows the collection and produces an iterator where each item is a refernce.
fn main() {
let nums = vec![3, 6, 9];
for n in nums.iter() {
println!("Reference to value: {}", n);
}
// nums is still usable ✅
println!("nums: {:?}", nums);
}
Basic Iterator Methods
Once you have an iterator, there are a number of methods that can be used. Some of the helpful ones are:
.sum()
— Add all the values together..min()
— Find the smallest value..max()
— Find the largest value..enumerate()
— Pairs each value with its index.
The .min()
and .max()
methods cannot reliably return the min/max of a Vec<f64>
because of floating point weirdness. Instead they return Option<>
which we will discuss later. Just know that they exist.
The sum()
method would’ve been nice earlier, huh 😉. The .sum()
method needs to have a type specified when using it. So we can specify the type when assigning the variable such as:
fn main() {
let nums = vec![2, 4, 8];
let total: i32 = nums.iter().sum();
println!("Sum is: {}", total);
}
Enumerated iterators
Often it is quite nice to have the index associated with each item in an iterator. To do so, we can use .enumerate()
after calling either .iter()
or .into_iter()
. This modifies each element to be a tuple with (i, xi)
.
The .enumerate()
method returns the type usize
which is a special type of unsigned integer. You can cast them to integers or floats using i as i32
, for example.
Rust is 0 indexed so the first value of the enumeration is going to be 0
.
For example:
fn is_even(x: i32) -> bool {
% 2 == 0
x }
fn main() {
let x = vec![2, 4, 8];
for (i, xi) in x.iter().enumerate() {
if is_even(i as i32) {
println!("i is even with value {xi}");
}
}
}
Exercise 1
- Modify the
mean()
function to calculate the mean using.iter()
- Create a vector of 5 or more
f64
values - Calculate the mean and print the result
Solution
View solution
fn mean(x: &[f64]) -> f64 {
let total: f64 = x.iter().sum();
/ x.len() as f64
total }
fn main() {
let nums = vec![1.0, 2.0, 3.0, 4.0, 5.0];
println!("Mean is: {}", mean(&nums));
}
Exercise 2
- Print the index and value for each item in a vector for only even values
Solution
View solution
fn main() {
let nums = vec![1, 2, 3, 4, 5, 6];
for (i, n) in nums.iter().enumerate() {
if n % 2 == 0 {
println!("Index {i}: {n} is even");
}
}
}