21 Rustlings Iterators Solution

Iterators

Rust's journey towards becoming one of the most loved programming languages isn't just by accident. At the heart of its rapidly growing ecosystem are several features that make it both powerful and unique. Among these, iterators stand out as a foundational concept, bridging the gap between functionality and performance. In this section, we'll look into the world of iterators in Rust, unraveling their elegance and efficiency. By understanding and mastering iterators, you'll unlock a significant aspect of Rust's capabilities, enabling you to write code that's both expressive and performant.

Further information

Iterators1.rs

// iterators1.rs
//
//  Make me compile by filling in the `???`s
//
// When performing operations on elements within a collection, iterators are essential.
// This module helps you get familiar with the structure of using an iterator and
// how to go through elements within an iterable collection.
//
// Execute `rustlings hint iterators1` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

fn main() {
    let my_fav_fruits = vec!["banana", "custard apple", "avocado", "peach", "raspberry"];

    let mut my_iterable_fav_fruits = ???;   // TODO: Step 1

    assert_eq!(my_iterable_fav_fruits.next(), Some(&"banana"));
    assert_eq!(my_iterable_fav_fruits.next(), ???);     // TODO: Step 2
    assert_eq!(my_iterable_fav_fruits.next(), Some(&"avocado"));
    assert_eq!(my_iterable_fav_fruits.next(), ???);     // TODO: Step 3
    assert_eq!(my_iterable_fav_fruits.next(), Some(&"raspberry"));
    assert_eq!(my_iterable_fav_fruits.next(), ???);     // TODO: Step 4
}

In this exercise we're looking at how to make this code compile in this line: let mut my_iterable_fav_fruits = ???; as our first step. Before we proceed, let's look at the errors.

Iterators1.rs Errors

⚠️  Compiling of exercises/iterators/iterators1.rs failed! Please try again. Here's the output:
error: expected expression, found `?`
  --> exercises/iterators/iterators1.rs:16:38
   |
16 |     let mut my_iterable_fav_fruits = ???;   // TODO: Step 1
   |                                      ^ expected expression

error: aborting due to previous error

We get an obvious error due to the missing expression in the variable assignment with the ??? so we have to implement an iterator. Let's solve this.

Iterators1.rs Solution

With iterators we can easily go through an array (or vector in this case) of elements. If you're not familiar with iterators, they're rather easy to use. In our situation here we have a vector which is my_fav_fruits that has a list of fruits. We see in the next line of code a new mutable variable my_iterable_fav_fruits where we know we have to complete this. So how do we do it? Easy, we add the .iter() method to the end of our variable that represents our vector, like this my_fav_fruits.iter() the full line will now look like this:

let mut my_iterable_fav_fruits = my_fav_fruits.iter(); // TODO: Step 1

The next part of TODO's are in the assert_eq! section of the code:

    assert_eq!(my_iterable_fav_fruits.next(), Some(&"banana"));
    assert_eq!(my_iterable_fav_fruits.next(), ???);     // TODO: Step 2
    assert_eq!(my_iterable_fav_fruits.next(), Some(&"avocado"));
    assert_eq!(my_iterable_fav_fruits.next(), ???);     // TODO: Step 3
    assert_eq!(my_iterable_fav_fruits.next(), Some(&"raspberry"));
    assert_eq!(my_iterable_fav_fruits.next(), ???);     // TODO: Step 4

And it looks pretty straightforward to follow the pattern of adding Some(&"fruit") as we already have some examples with banana, avocado, and raspberry. The only one that should be different is the final one since after raspberry there is nothing in the vector. So the logical answer would be None right? Let's try this out.

fn main() {
    let my_fav_fruits = vec!["banana", "custard apple", "avocado", "peach", "raspberry"];

    let mut my_iterable_fav_fruits = my_fav_fruits.iter();   // TODO: Step 1

    assert_eq!(my_iterable_fav_fruits.next(), Some(&"banana"));
    assert_eq!(my_iterable_fav_fruits.next(), Some(&"custard apple"));     // TODO: Step 2
    assert_eq!(my_iterable_fav_fruits.next(), Some(&"avocado"));
    assert_eq!(my_iterable_fav_fruits.next(), Some(&"peach"));     // TODO: Step 3
    assert_eq!(my_iterable_fav_fruits.next(), Some(&"raspberry"));
    assert_eq!(my_iterable_fav_fruits.next(), None);     // TODO: Step 4
}

With those changes as defined above we are compiling, great let's move on to the next exercise.

✅ Successfully ran exercises/iterators/iterators1.rs!
🎉 🎉  The code is compiling! 🎉 🎉

Iterators2.rs

// iterators2.rs
// In this exercise, you'll learn some of the unique advantages that iterators
// can offer. Follow the steps to complete the exercise.
// Execute `rustlings hint iterators2` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

// Step 1.
// Complete the `capitalize_first` function.
// "hello" -> "Hello"
pub fn capitalize_first(input: &str) -> String {
    let mut c = input.chars();
    match c.next() {
        None => String::new(),
        Some(first) => ???,
    }
}

// Step 2.
// Apply the `capitalize_first` function to a slice of string slices.
// Return a vector of strings.
// ["hello", "world"] -> ["Hello", "World"]
pub fn capitalize_words_vector(words: &[&str]) -> Vec<String> {
    vec![]
}

// Step 3.
// Apply the `capitalize_first` function again to a slice of string slices.
// Return a single string.
// ["hello", " ", "world"] -> "Hello World"
pub fn capitalize_words_string(words: &[&str]) -> String {
    String::new()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_success() {
        assert_eq!(capitalize_first("hello"), "Hello");
    }

    #[test]
    fn test_empty() {
        assert_eq!(capitalize_first(""), "");
    }

    #[test]
    fn test_iterate_string_vec() {
        let words = vec!["hello", "world"];
        assert_eq!(capitalize_words_vector(&words), ["Hello", "World"]);
    }

    #[test]
    fn test_iterate_into_string() {
        let words = vec!["hello", " ", "world"];
        assert_eq!(capitalize_words_string(&words), "Hello World");
    }
}

Iterators2.rs Errors

⚠️  Compiling of exercises/iterators/iterators2.rs failed! Please try again. Here's the output:
error: expected expression, found `?`
  --> exercises/iterators/iterators2.rs:15:24
   |
15 |         Some(first) => ???,
   |                     -- ^ expected expression
   |                     |
   |                     while parsing the `match` arm starting here

error: aborting due to previous error

our errors are to be expected with our incomplete functions, so let's move on to

Iterators2.rs Solution

// Step 1.
// Complete the `capitalize_first` function.
// "hello" -> "Hello"
pub fn capitalize_first(input: &str) -> String {
    let mut c = input.chars();
    match c.next() {
        None => String::new(),
        Some(first) => first.to_uppercase().collect::<String>() + c.as_str(),
    }
}

// Step 2.
// Apply the `capitalize_first` function to a slice of string slices.
// Return a vector of strings.
// ["hello", "world"] -> ["Hello", "World"]
pub fn capitalize_words_vector(words: &[&str]) -> Vec<String> {
    words.iter().map(|x| capitalize_first(x)).collect()
}

// Step 3.
// Apply the `capitalize_first` function again to a slice of string slices.
// Return a single string.
// ["hello", " ", "world"] -> "Hello World"
pub fn capitalize_words_string(words: &[&str]) -> String {
    capitalize_words_vector(words).join("")
}

This is one of the more involved solutions we've seen in a while, so let's go over each step and what we did to finish each function and make our code compile and pass the tests.

  1. Step 1 - capitalize_first function:

    • We have a function called capitalize_first that takes a reference to a string (&str) as input.
    • Inside the function, we used the .chars() method to get an iterator over the characters in the input string.
    • We then used a match expression to handle two cases:
      • If the iterator is empty (i.e., the input string is empty), we returned an empty String.
      • If there is at least one character, we used .next() to get the first character and then applied the .to_uppercase() method to convert it to uppercase.
      • Finally, we used .collect::<String>() to collect the uppercase character and the rest of the characters (using c.as_str()) into a new String.
  2. Step 2 - capitalize_words_vector function:

    • We complete a function called capitalize_words_vector that takes a slice of string slices (&[&str]) as input.
    • Inside the function, we used .iter() to iterate over the elements in the input slice (which are string slices).
    • For each string slice, we applied the capitalize_first function to capitalize its first character and collected the results into a Vec<String> using .map() and .collect().
  3. Step 3 - capitalize_words_string function:

    • We created a function called capitalize_words_string that takes a slice of string slices (&[&str]) as input.
    • Inside the function, we reused the capitalize_words_vector function to get a vector of capitalized words.
    • To create a single string, we used the .join("") method, which concatenates all the elements in the vector without any spaces between them.

Overall, the solution focuses on creating reusable functions (capitalize_first and capitalize_words_vector) to handle the capitalization of individual words and then uses these functions to achieve the desired output for the exercise (capitalize_words_string). This approach makes the code more modular and easier to understand.

✅ Successfully tested exercises/iterators/iterators2.rs!

🎉 🎉  The code is compiling, and the tests pass! 🎉 🎉

With that we have passed the test, let's continue to our next exercise.

Iterators3.rs

// iterators3.rs
// This is a bigger exercise than most of the others! You can do it!
// Here is your mission, should you choose to accept it:
// 1. Complete the divide function to get the first four tests to pass.
// 2. Get the remaining tests to pass by completing the result_with_list and
//    list_of_results functions.
// Execute `rustlings hint iterators3` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

#[derive(Debug, PartialEq, Eq)]
pub enum DivisionError {
    NotDivisible(NotDivisibleError),
    DivideByZero,
}

#[derive(Debug, PartialEq, Eq)]
pub struct NotDivisibleError {
    dividend: i32,
    divisor: i32,
}

// Calculate `a` divided by `b` if `a` is evenly divisible by `b`.
// Otherwise, return a suitable error.
pub fn divide(a: i32, b: i32) -> Result<i32, DivisionError> {
    todo!();
}

// Complete the function and return a value of the correct type so the test passes.
// Desired output: Ok([1, 11, 1426, 3])
fn result_with_list() -> () {
    let numbers = vec![27, 297, 38502, 81];
    let division_results = numbers.into_iter().map(|n| divide(n, 27));
}

// Complete the function and return a value of the correct type so the test passes.
// Desired output: [Ok(1), Ok(11), Ok(1426), Ok(3)]
fn list_of_results() -> () {
    let numbers = vec![27, 297, 38502, 81];
    let division_results = numbers.into_iter().map(|n| divide(n, 27));
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_success() {
        assert_eq!(divide(81, 9), Ok(9));
    }

    #[test]
    fn test_not_divisible() {
        assert_eq!(
            divide(81, 6),
            Err(DivisionError::NotDivisible(NotDivisibleError {
                dividend: 81,
                divisor: 6
            }))
        );
    }

    #[test]
    fn test_divide_by_0() {
        assert_eq!(divide(81, 0), Err(DivisionError::DivideByZero));
    }

    #[test]
    fn test_divide_0_by_something() {
        assert_eq!(divide(0, 81), Ok(0));
    }

    #[test]
    fn test_result_with_list() {
        assert_eq!(format!("{:?}", result_with_list()), "Ok([1, 11, 1426, 3])");
    }

    #[test]
    fn test_list_of_results() {
        assert_eq!(
            format!("{:?}", list_of_results()),
            "[Ok(1), Ok(11), Ok(1426), Ok(3)]"
        );
    }
}

Okay, this is a longer one, so let's breakdown the instructions.

  1. Complete the divide function
  2. Complete the results_with_list function
  3. Complete the list_of_results function

Iterators3.rs Errors

⚠️  Testing of exercises/iterators/iterators3.rs failed! Please try again. Here's the output:

running 6 tests
test tests::test_divide_0_by_something ... FAILED
test tests::test_not_divisible ... FAILED
test tests::test_divide_by_0 ... FAILED
test tests::test_result_with_list ... FAILED
test tests::test_list_of_results ... FAILED
test tests::test_success ... FAILED

successes:

successes:

failures:

---- tests::test_divide_0_by_something stdout ----
thread 'tests::test_divide_0_by_something' panicked at exercises/iterators/iterators3.rs:26:5:
not yet implemented

---- tests::test_not_divisible stdout ----
thread 'tests::test_not_divisible' panicked at exercises/iterators/iterators3.rs:26:5:
not yet implemented

---- tests::test_divide_by_0 stdout ----
thread 'tests::test_divide_by_0' panicked at exercises/iterators/iterators3.rs:26:5:
not yet implemented
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

---- tests::test_result_with_list stdout ----
thread 'tests::test_result_with_list' panicked at exercises/iterators/iterators3.rs:75:9:
assertion `left == right` failed
  left: "()"
 right: "Ok([1, 11, 1426, 3])"

---- tests::test_list_of_results stdout ----
thread 'tests::test_list_of_results' panicked at exercises/iterators/iterators3.rs:80:9:
assertion `left == right` failed
  left: "()"
 right: "[Ok(1), Ok(11), Ok(1426), Ok(3)]"

---- tests::test_success stdout ----
thread 'tests::test_success' panicked at exercises/iterators/iterators3.rs:26:5:
not yet implemented


failures:
    tests::test_divide_0_by_something
    tests::test_divide_by_0
    tests::test_list_of_results
    tests::test_not_divisible
    tests::test_result_with_list
    tests::test_success

test result: FAILED. 0 passed; 6 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

We see that we have a total 6 tests and they're all failing. The first 3 are because the functions are not yet implemented as our compiler confirms, the next 2 show us that the left is not matching the right and our final test is also because it's not yet implemented.

Iterators3.rs Solution

1. Complete the divide function

Our instructions to complete the divide function are the following:

// Calculate `a` divided by `b` if `a` is evenly divisible by `b`.
// Otherwise, return a suitable error.
pub fn divide(a: i32, b: i32) -> Result<i32, DivisionError> {
    todo!();
}

With this function we actually have a couple of different ways of solving it, we can use the classic if statement or use a more "Rusty" approach by using match. Let's first look at the more traditional method of completing this function. Our instructions tell is that we have to calculate if a can be evenly divided by b if, it can we do it, if not we return an error. If we look our enum DivisionError we can see that we have 2 possible errors, so that gives us a total of 3 possible scenarios:

  1. Ok(): if a is divisible by b and we divided
  2. Err(): if we are trying to divide by so if b == 0 so we return the DividebyZeroerror.
  3. Err(): if a is not evenly divisible by b we return NotDivisible(NotDivisibleError)

Alright let's implement this. Note, that we should implement the if b == 0 first to make sure that b is not zero before proceeding to a % b.

pub fn divide(a: i32, b: i32) -> Result<i32, DivisionError> {
    if b == 0 {
        // If the divisor is zero, return the DivideByZero error.
        Err(DivisionError::DivideByZero)
    } else if a % b == 0 {
        // If 'a' is evenly divisible by 'b', return the result.
        Ok(a / b)
    } else {
        // If 'a' is not evenly divisible by 'b', return the NotDivisible error.
        Err(DivisionError::NotDivisible(NotDivisibleError {
            dividend: a,
            divisor: b,
        }))
    }
}

Now let's look at this alternative version using using match:

pub fn divide(a: i32, b: i32) -> Result<i32, DivisionError> {
    match (a, b) {
	    // first check if the divisor is zero
        (_, 0) => Err(DivisionError::DivideByZero),
        // then check if (x, y) are evenly divisible
        (x, y) if x % y == 0 => Ok(x / y),
        // lastly return an error if it's not even divisible
        (x, y) => Err(DivisionError::NotDivisible(NotDivisibleError {
            dividend: x,
            divisor: y,
        })),
    }
}

In this version:

  • We use a match expression to pattern match with a the tuple (a, b).
  • The first arm (_, 0) => Err(DivisionError::DivideByZero) matches when the divisor b is zero, and it returns the DivideByZero error.
  • The second arm (x, y) if x % y == 0 => Ok(x / y) matches when a is evenly divisible by b, and it returns the result of the division as Ok.
  • The third arm (x, y) is a catch-all that matches any other case where a is not evenly divisible by b. It returns the NotDivisible error with the appropriate NotDivisibleError struct.

Let's save at this point and see what happens:

test result: FAILED. 4 passed; 2 failed;

Great, it looks like we have passed 4 tests, by completing that first function.

2. Complete the results_with_list function

These are the instructions to help us complete this function.

// Complete the function and return a value of the correct type so the test passes.
// Desired output: Ok([1, 11, 1426, 3])
fn result_with_list() -> () {
    let numbers = vec![27, 297, 38502, 81];
    let division_results = numbers.into_iter().map(|n| divide(n, 27));
}

So, let's add a return type, if we look at the rest of the function you can see that we are using a Vec so, that makes it pretty straight forward to understand what we have to return right?

fn result_with_list() -> Result<Vec<i32>, DivisionError> {

Our function signature should be updated to handle returning a Vec<i32> if all is Ok or a DivisionError if it's not so we put that in the function signature to complete it. Next let's format the division_results function a little differently from the one liner to the having each method start on a new line.

let division_results = numbers
    .into_iter()
    .map(|n| divide(n, 27))
    .collect();

This also give us the opportunity to add the .collect() method which was missing and essentially when "collecting" elements back into a vector. This also makes it easier to read in my opinion.

Finally to complete this function we have to return a value. Right now if we compile we'd see this message:

40 | fn result_with_list() -> Result<Vec<i32>, DivisionError> {
   |    ----------------      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Result<Vec<i32>, DivisionError>`, found `()`
   |    |
   |    implicitly returns `()` as its body has no tail or `return` expression
   |
   = note:   expected enum `Result<Vec<i32>, DivisionError>`
           found unit type `()`
help: consider returning the local binding `division_results`
   |
45 ~         .collect();
46 +     division_results

Luckily the Rust's error message is explicit in telling you what you should return. If we add division_results on line 46 we get this printout:

running 6 tests
test tests::test_divide_0_by_something ... ok
test tests::test_divide_by_0 ... ok
test tests::test_not_divisible ... ok
test tests::test_result_with_list ... ok
test tests::test_success ... ok
test tests::test_list_of_results ... FAILED

Alright we're almost there.

3. Complete the list_of_results function

This is our final function to complete as you can see we are passing all our tests except our final one, so let's dig into the instructions for this one.

// Complete the function and return a value of the correct type so the test passes.
// Desired output: [Ok(1), Ok(11), Ok(1426), Ok(3)]
fn list_of_results() -> () {
    let numbers = vec![27, 297, 38502, 81];
    let division_results = numbers.into_iter().map(|n| divide(n, 27));
}

Our starting point looks very similar to our previous function but the desired output is different. As it seems to be wanting one Ok result for every number vs a Vec<i32>. So how do we solve this?

Instead of wrapping our Vec<i32> in Result we wrap our Result in a Vec<Result<i32>> like so:

fn list_of_results() -> Vec<Result<i32, DivisionError>> {

and then like in our previous example, we use the .collect() method and returndivision_results. Here's the full block for reference.

fn list_of_results() -> Vec<Result<i32, DivisionError>> {
    let numbers = vec![27, 297, 38502, 81];
    let division_results: Vec<Result<i32, DivisionError>> = numbers
        .into_iter()
        .map(|n| divide(n, 27))
        .collect();

    division_results
}
  1. We specify the return type as Vec<Result<i32, DivisionError>> to match the desired output.
  2. We use the collect method on the iterator of division results. This method collects the individual results into a Vec<Result<i32, DivisionError>>.
  3. We store the collected results in the division_results variable.
  4. Finally, we return division_results, which is a Vec containing individual Ok results.

This should make the test pass with the desired output [Ok(1), Ok(11), Ok(1426), Ok(3)].

✅ Successfully tested exercises/iterators/iterators3.rs!

🎉 🎉  The code is compiling, and the tests pass! 🎉 🎉

Iterators4.rs

// iterators4.rs
// Execute `rustlings hint iterators4` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

pub fn factorial(num: u64) -> u64 {
    // Complete this function to return the factorial of num
    // Do not use:
    // - return
    // Try not to use:
    // - imperative style loops (for, while)
    // - additional variables
    // For an extra challenge, don't use:
    // - recursion
    // Execute `rustlings hint iterators4` for hints.
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn factorial_of_0() {
        assert_eq!(1, factorial(0));
    }

    #[test]
    fn factorial_of_1() {
        assert_eq!(1, factorial(1));
    }
    #[test]
    fn factorial_of_2() {
        assert_eq!(2, factorial(2));
    }

    #[test]
    fn factorial_of_4() {
        assert_eq!(24, factorial(4));
    }
}

Okay it seems we have a little challenge here to create a function that returns the factorial of num we also have some added limitations.

  1. Do not use return
  2. Try not use for or while
  3. Additional variables
  4. Extra credit: Don't use recursion

Iterators4.rs Errors

⚠️  Compiling of exercises/iterators/iterators4.rs failed! Please try again. Here's the output:
error[E0308]: mismatched types
 --> exercises/iterators/iterators4.rs:6:31
  |
6 | pub fn factorial(num: u64) -> u64 {
  |        ---------              ^^^ expected `u64`, found `()`
  |        |
  |        implicitly returns `()` as its body has no tail or `return` expression
  |
help: consider returning the local binding `num`
  |
6 ~ pub fn factorial(num: u64) -> u64 {
7 +     num
  |
error: aborting due to previous error

Our errors don't tell us much in this case we know we have an incomplete function

Iterators4.rs Solution

So our challenge is pretty wide open, meaning we do have a possible few different ways to solve this, but let's try to stick as closely as possible to the requirements. It's clear this exercise is made for us to iterators and their associated methods.

But before we do that let's make sure we understand the goal, just incase some of us need to brush up on our math terminology, let's make sure we understand the definition of a factorial.

What is a Factorial?

Factorials are a fascinating concept in mathematics that involves multiplying a sequence of numbers together.

Simply put, a factorial of a number is the product of all positive integers from 1 to that number. For example, the factorial of 5 is calculated as:

5! = 5 × 4 × 3 × 2 × 1 = 120

In Rust, you can do this easily and efficiently using a few simple methods.

So we said that to calculate a factorial we need a sequence of numbers from 1 to that numbers so how do we represent this in Rust?1..num seems like a logical choice. Right, so now we have to take this range and iterate over it but instead of using iter let's use into_iter which allows us to take ownership of each element and then we use .product() which we haven't used before but comes in handy in situations like these, more on product below.

pub fn factorial(num: u64) -> u64 {
    (1..=num).into_iter().product()
}

.product() Notes

product() method is a convenient way to calculate the product of all the numbers in an iterator or a range. In the context of calculating a factorial, think of it like this:

Let's say you want to find the factorial of a number, like 5! (read as "5 factorial"). This means you want to multiply all the positive whole numbers from 1 to 5 together: 1 * 2 * 3 * 4 * 5.

Using an iterator or a range, you can create a sequence of these numbers: 1, 2, 3, 4, 5. Then, when you apply the product() method to this sequence, it will perform the multiplication for you and give you the result, which in this case is 120.

So, in essence, product() takes a sequence of numbers and multiplies them all together, making it handy for tasks like calculating factorials. It's like a built-in calculator for products in Rust!

So if we save this we will successfully compile.

Let's move on to our final exercise.

Iterators5.rs

// iterators5.rs
// Let's define a simple model to track Rustlings exercise progress. Progress
// will be modelled using a hash map. The name of the exercise is the key and
// the progress is the value. Two counting functions were created to count the
// number of exercises with a given progress. These counting functions use
// imperative style for loops. Recreate this counting functionality using
// iterators. Only the two iterator methods (count_iterator and
// count_collection_iterator) need to be modified.
// Execute `rustlings hint iterators5` or use the `hint` watch subcommand for a hint.
//
// Make the code compile and the tests pass.

// I AM NOT DONE

use std::collections::HashMap;

#[derive(Clone, Copy, PartialEq, Eq)]
enum Progress {
    None,
    Some,
    Complete,
}

fn count_for(map: &HashMap<String, Progress>, value: Progress) -> usize {
    let mut count = 0;
    for val in map.values() {
        if val == &value {
            count += 1;
        }
    }
    count
}

fn count_iterator(map: &HashMap<String, Progress>, value: Progress) -> usize {
    // map is a hashmap with String keys and Progress values.
    // map = { "variables1": Complete, "from_str": None, ... }
    todo!();
}

fn count_collection_for(collection: &[HashMap<String, Progress>], value: Progress) -> usize {
    let mut count = 0;
    for map in collection {
        for val in map.values() {
            if val == &value {
                count += 1;
            }
        }
    }
    count
}

fn count_collection_iterator(collection: &[HashMap<String, Progress>], value: Progress) -> usize {
    // collection is a slice of hashmaps.
    // collection = [{ "variables1": Complete, "from_str": None, ... },
    //     { "variables2": Complete, ... }, ... ]
    todo!();
}

#[cfg(test)]
mod tests {
    use super::*;

// tests removed for space...

Our instructions are to finish 2 functions using iterators instead of imperative style loops.

Iterators5.rs Errors

⚠️  Testing of exercises/iterators/iterators5.rs failed! Please try again. Here's the output:

running 4 tests
test tests::count_complete ... FAILED
test tests::count_equals_for ... FAILED
test tests::count_collection_complete ... FAILED
test tests::count_collection_equals_for ... FAILED

For reference, here are failing tests, but this is expected as they are not implemented yet.

Iterators5.rs Solution

count_iterator solution

For the first function count_iterator we have additional note to help us complete our function

fn count_iterator(map: &HashMap<String, Progress>, value: Progress) -> usize {
    // map is a hashmap with String keys and Progress values.
    // map = { "variables1": Complete, "from_str": None, ... }
    todo!();
}

Here's a way we can solve this function

  • We use map.values() to obtain an iterator over the values in the hashmap.
  • We use filter to keep only the values that are equal to the given value.
  • Finally, we call count() to count the number of elements that match the condition.
fn count_iterator(map: &HashMap<String, Progress>, value: Progress) -> usize {
    map.values()
    .filter(|&&val| val == value)
    .count()
}

This should complete our function and move us closer to compiling.

count_collection_iterator solution


fn count_collection_iterator(collection: &[HashMap<String, Progress>], value: Progress) -> usize {
    // collection is a slice of hashmaps.
    // collection = [{ "variables1": Complete, "from_str": None, ... },
    //     { "variables2": Complete, ... }, ... ]
    todo!();
}```
again a similar situation where we have to complete our function with additional notes and instructions in the comments.

We can approach this function like so:
- We use `collection.iter()` to get an iterator over the slices of hashmaps.
- We use `flat_map` to flatten each slice of hashmap values into a single iterator of values.
- Then, we apply the same `filter` and `count` operations as in `count_iterator` to count the matching values across all hashmaps in the collection.

Our final code block looks like this:
```rust
fn count_collection_iterator(collection: &[HashMap<String, Progress>], value: Progress) -> usize {
    collection.iter()
    .flat_map(|map| map.values())
    .filter(|&&val| val == value)
    .count()
}

And with this we are compiling and passing our tests!

I apologize for the oversight. Let's wrap up your blog with a suitable conclusion.

Conclusion

In these rustlings exercises, we've traversed the intriguing waters of Rust's iterators, pattern matching, and handling results. Rust, being a language that prioritizes safety and efficiency, provides tools like match and Result to handle various scenarios gracefully. The real power of Rust lies in its expressive type system and the way it allows us to catch errors at compile time. By practicing with iterators and learning to handle errors the "Rusty" way, we're not just writing more concise code; we're embracing a paradigm that avoids common pitfalls seen in other languages.

Through our journey of solving the provided exercises, we have reinforced the idea that Rust provides an elegant balance between performance and safety. Its functional capabilities, combined with its low-level control, make it a top choice for systems programming and beyond. If you're still new to Rust, I hope this exploration has provided clarity and fueled your interest to dig deeper. If you're already a seasoned Rustacean, I hope this was a refreshing recap and perhaps even offered a new perspective or two.

In the world of programming, continuous learning is the key. As we've seen today, even seemingly simple exercises can offer valuable insights. Keep coding, keep exploring, and always strive to better your understanding. The Rust community is vast, welcoming, and ever-growing. With tools like rustlings and a plethora of online resources, your Rust journey is bound to be exciting and enlightening. Happy coding!