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:
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().
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.
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.
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?
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:
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:
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).
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.