Stuck on a Rustlings problem (standard_library_types/iterators5.rs)

I'm working through the Rustlings set of exercises. Doing overall OK so far, but I'm completely stuck on the second part of standard_library_types/iterators5.rs, in which I'm supposed to write a function that iterates over a collections of maps.

My code tries to reuse the same iterator-based function that I wrote for the first half of this exercise. The code for these two functions is:

fn count_iterator(map: &HashMap<String, Progress>, value: Progress) -> usize {
    map.values().into_iter().filter(|&v| *v == value).count()
}

fn count_collection_iterator(collection: &[HashMap<String, Progress>], value: Progress) -> usize {
    collection
        .iter()
        .map(|m| count_iterator(m, value))
        .fold(0, |acc, n| acc + n)
}

However, the second function results in a compilation error:

! Compiling of exercises/standard_library_types/iterators5.rs failed! Please try again. Here's the output:
error[E0507]: cannot move out of `value`, a captured variable in an `FnMut` closure
  --> exercises/standard_library_types/iterators5.rs:55:36
   |
52 | fn count_collection_iterator(collection: &[HashMap<String, Progress>], value: Progress) -> usize {
   |                                                                        ----- captured outer variable
...
55 |         .map(|m| count_iterator(m, value))
   |                                    ^^^^^ move occurs because `value` has type `Progress`, which does not implement the `Copy` trait

error: aborting due to previous error

I've been through all the docs I can find, and I cannot figure out how to pass the value parameter into the calls to the fn being called within map().

Randy

2 Likes

Hi, have you try implementing count_iterator like this:

fn count_iterator(map: &HashMap<String, Progress>, value: &Progress) -> usize {
    //                                                    ^ borrow Progress
    map.values().into_iter().filter(|&v| v == value).count()
}

and then in your count_collection_iterator function, pass a borrow of value instead of move into the count_iterator function

fn count_collection_iterator(collection: &[HashMap<String, Progress>], value: Progress) -> usize {
    collection
        .iter()
        .map(|m| count_iterator(m, &value))
        //                         ^ pass a borrow
        .fold(0, |acc, n| acc + n)
}

Here is the playground link.

1 Like

I considered that at first, but the prelude comment in the file (iterators5.rs) said that it was only necessary to change the two functions (count_iterator and count_collection_iterator). If I changed the signature of count_iterator, I would then have to change the tests later in the file as well.

1 Like

It doesn't seems like the rustling intended to reuse the count_iterator() function there. There's no way to duplicate the Progress and you can't pass it by reference without modify the surrounding code.

1 Like
fn count_collection_iterator(collection: &[HashMap<String, Progress>], value: Progress) -> usize {

    let mut d = 0;
    for map in collection {
        d += map
            .iter()
            .filter( |&(_x, val)| val == &value)
            .count() 
    }
    d

This compiles and passes the tests. But I can't work out how to do what I think the challenge is asking, which is to not need to use for at all. Can anybody show me how this could be made to work replacing the for with another iterator?

And why doesn't this work?!

let mut c = collection.iter().map( |h| h.values().filter( | &val | val == &value)).count();
c

It compiles but it doesn't get the right answer.

Let's split this up to make it more comprehensible. In your count_collection_iterator, inside of your for loop, you're doing the same thing as count_iterator. So let's start from:

fn count_iterator(map: &HashMap<String, Progress>, value: Progress) -> usize {
    map.iter().filter( |&(_x, val)| val == &value).count() 
}

fn count_collection_iterator(collection: &[HashMap<String, Progress>], value: Progress) -> usize {
    let mut d = 0;
    for map in collection {
        d += count_iterator(map, value);
    }
    d
}

We still need to get rid of the for loop. Here's the attempt in your second post, refactored:

// Wrong Answers
fn count_collection_iterator(collection: &[HashMap<String, Progress>], value: Progress) -> usize {
    collection.iter().map(|h| count_iterator(h, value)).count()
}

The problem is that you're not adding up the results from count_iterator, you're just counting how many results there are. So this is the same as counting the number of HashMaps in collection.

Instead of counting them, you want to sum them up. Fortunately, that method exists too:

fn count_collection_iterator(collection: &[HashMap<String, Progress>], value: Progress) -> usize {
    collection.iter().map(|h| count_iterator(h, value)).sum()
                                                     // ^^^^^ this changed
}

Playground.

2 Likes

Thanks for your reply! I learned something! I must have misunderstood the exercise, though. I’m familiar with functional programming as a concept but as someone new to programming in general it wasn’t obvious to me that you needed to call the first function as part of the second. Note that the ‘solution’ for the second function that I had with the for loop solves the challenge without needing to call the first function or needing to implement #[derive(Clone, Copy)] for Progress. So I was wondering whether anyone could show me how to do that using iterators only with no for loop.

Yeah, you don't have to call the first solution from the second, but I found it easier to read (and it also satisfies the DRY principle -- Don't Repeat Yourself).

Except for using count() instead of sum(), you were already there. I.e. you can just do what you did in the first solution within the body of the map closure.

fn count_collection_iterator(collection: &[HashMap<String, Progress>], value: Progress) -> usize {
    collection.iter().map(|h| 
        h.iter().filter( |&(_x, val)| val == &value).count() 
    ).sum()
}
1 Like

Thank you! I take your point about DRY, but THIS is the solution I've been trying to understand. It was that second .iter() within the |h| closure that I wasn't getting.

While we're here, could anyone explain why we need the sum() at the end here and what is going on in general with count() and sum() in general with this rustling?

.count() tells you how many items there are in the iterator. Specifically here, because of the construction .filter(...).count(), you're getting "how many elements match this predicate".

Now, that's the inner loop - the one over a HashMap. In the outer iteratior, you're transforming Iterator<Item = &HashMap<...>> into Iterator<Item = usize>. For that, you need .sum(), because you're not just figuring out how many elements there are, you're suming all the numbers.

Another possibility is to instead use .flat_map(|map| map.iter()) to convert Iterator<Item = &HashMap<String, Progress>> into Iterator<Item = (&String, &Progress)> (essentially chaining all the key-value pairs into one long Iterator). You could then do the .filter(...).count() on the chained iterator.

2 Likes

Thank you very much!
collection.iter().flat_map( |map| map.iter()).filter( | &(_x, val) | val == &value).count()

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.