I'm learning Rust using Advent of Code.
To resolve day 04 2021, I need to parse the following string:
88 67 20 19 15
22 76 86 44 73
7 42 6 69 25
12 68 92 21 75
97 45 13 52 70
75 98 24 18 77
17 93 46 49 13
92 56 97 57 66
44 0 65 54 74
23 6 53 42 20
92 94 9 27 41
73 28 62 90 40
78 3 12 37 32
8 86 91 16 30
84 38 68 11 19
Ideally, I would like to generate nested vectors Vec<Vec<&str>>
.
My problem is that I don't understand why the following code doesn't work.
let bingo_sheet = grids_str
split("\r\n\r\n")
.map(|grid| grid.split_whitespace())
.collect::<Vec<Vec<&str>>>();
I also don't understand the meaning behind cargo check. (Ok the trait doesn't exist. But why?)
error[E0277]: a value of type `Vec<Vec<&str>>` cannot be built from an iterator over elements of type `SplitWhitespace<'_>`
--> src\main.rs:36:10
|
36 | .collect::<Vec<Vec<&str>>>();
| ^^^^^^^ value of type `Vec<Vec<&str>>` cannot be built from `std::iter::Iterator<Item=SplitWhitespace<'_>>`
|
= help: the trait `FromIterator<SplitWhitespace<'_>>` is not implemented for `Vec<Vec<&str>>`
After a bit of experimentation this usage of flat_map
seems to work.
let bingo_sheet = grids_str
.split("\r\n\r\n")
.flat_map(|grid| grid.split_whitespace())
.collect::<Vec<&str>>();
I don't understand what is happening here.
The flat_map
code behaves as expected, but not the map
code.
CodePudding user response:
The question has been answered briefly in the comments already while I wrote this but maybe a more detailed version still helps!
I also don't understand the meaning behind cargo check. (Ok the trait doesn't exist. But why?)
The std provides conversions that are used often but it doesn't do all of them for you.
the call to split_whitespace
returns a SplitWhitespace
struct that implements (in the rest of this I will simply say "is") Iterator
.
the call to map
returns a Map
struct which is an iterator over the return type of its given function, so it's an Iterator over an Iterator over string slices.
Because Map
is an iterator, it has a collect
method. As you can see in the docs, this method exists if there is a B
that implements FromIterator<Self::Item>
. This B
is your Vec<Vec<&str>>
and Self::Item
is the item of Map
, the SplitWhiteSpace
struct. So collect
is asking, "does Vec<Vec<&str>>
implement FromIterator<SplitWhiteSpace>
?, if yes, I exist, if not, I refuse to exist".
(FromIterator<SplitWhiteSpace>
does not mean that Vec<T>
can be made from the SplitWhiteSpace
struct but that it can be made from some type I
that is (or can be made into) an iterator over the item SplitWhiteSpace
.)
So let's look into the Vec docs and find out if Vec<Vec<&str>>
implements FromIterator<SplitWhiteSpace>
. Vec
does implement FromIterator<T>
but only for Vec<T>
, so in your case it implements FromIterator<Vec<&str>>
but collect is asking it to implement FromIterator<SplitWhiteSpace>
. It doesn't so your code doesn't compile.
In other words, when collecting a Map<X>
into a Vec<Y>
, X has to equal Y.
And this is why adding a collect()
inside the .map
works. Because that makes it a Map<Vec<&str>>
which means the types match. flatmap
also works because that creates a FlatMap<&str>
which works similarly to Map
and can be collected into a Vec<&str>
. (flat_map
does not need the collect
inside its given function because it can handle iterators inside it)