Skip to content

Commit 3a2453e

Browse files
committed
remove a step, refactor into unwrap advice
1 parent 7983713 commit 3a2453e

File tree

1 file changed

+87
-81
lines changed

1 file changed

+87
-81
lines changed

exercise-book/src/iterators.md

Lines changed: 87 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ For completing this exercise you need to have
1818

1919
## Task
2020

21+
2122
- Calculate the sum of all odd numbers in the following string using an iterator chain
2223

2324
```text
@@ -34,7 +35,9 @@ five
3435
X
3536
```
3637

37-
- Drop this snippet into your `src/main.rs` after you `cargo new iterators`.
38+
- Do `cargo new iterators`
39+
- Place the above multi-line string into `iterators/numbers.txt`.
40+
- Drop this snippet into your `src/main.rs`:
3841

3942
```rust
4043
#![allow(unused_imports)]
@@ -47,7 +50,7 @@ fn main() -> Result<(), Box<dyn Error>> {
4750
let f = File::open("../exercise-templates/iterators/numbers.txt")?;
4851
let reader = BufReader::new(f);
4952

50-
// Write your iterator chain here
53+
// Write your iterator chain here
5154
let sum_of_odd_numbers: i32 = todo!("use reader.lines() and Iterator methods");
5255

5356
assert_eq!(sum_of_odd_numbers, 31);
@@ -56,7 +59,6 @@ fn main() -> Result<(), Box<dyn Error>> {
5659

5760
```
5861

59-
- Place the above multi-line string into `iterators/numbers.txt`.
6062
- Replace the first `todo!` item with [reader.lines()](https://doc.rust-lang.org/stable/std/io/trait.BufRead.html#method.lines) and continue "chaining" the iterators until you've calculated the desired result.
6163
- Run the code with `cargo run --bin iterators1` when inside the `exercise-templates` directory if you want a starting template.
6264

@@ -100,6 +102,12 @@ The first point is not in vain - the original snippet has a bug in the upper bou
100102

101103
Think of iterators as lazy functions - they only carry out computation when a *consuming adapter* like `.collect()` is called, not the `.map()` itself.
102104

105+
### Iterator chains workflow advice
106+
107+
Start every iterator call on a new line, so that you can see closure arguments and type hints for the iterator at the end of the line clearly.
108+
109+
When in doubt, write `.map(|x| x)` first to see what item types you get and decide on what iterator methods to use and what to do inside a closure based on that.
110+
103111
### Turbo fish syntax `::<>`
104112

105113
Iterators sometimes struggle to figure out the types of all intermediate steps and need assistance.
@@ -116,6 +124,37 @@ let numbers: Vec<_> = ["1", "2", "3"]
116124

117125
This `::<SomeType>` syntax is called the [turbo fish operator](https://doc.rust-lang.org/book/appendix-02-operators.html?highlight=turbo%20fish#non-operator-symbols), and it disambiguates calling the same method with different output types, like `.parse::<i32>()` and `.parse::<f64>()` (try it!)
118126

127+
### Dealing with `.unwrap()`s in iterator chains
128+
129+
When starting out with iterators, it's very easy to be "led astray" by locally useful `.unwrap()`s as suggested by the compiler.
130+
131+
It's easy to get a slogging first solution with a lot of `Option` and `Result` wrapping and unwrapping that other languages wouldn't make explicit.
132+
133+
Concretely, the following snippet:
134+
135+
```rust
136+
137+
let numeric_lines = reader.lines()
138+
.map(|l| l.unwrap())
139+
.map(|s| s.parse::<i32>())
140+
.filter(|s| s.is_ok())
141+
//...
142+
143+
```
144+
145+
can be replaced with a judicious use of [.filter_map()](https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html#method.filter_map):
146+
147+
```rust
148+
let numeric_lines = reader.lines()
149+
.filter_map(|line| line.ok())
150+
.filter_map(|s| s.parse().ok())
151+
//...
152+
```
153+
154+
You will relive similar experiences when learning Rust without knowing the right tools from the standard library that let you convert `Result` into what you actually need.
155+
156+
We make a special emphasis on avoiding "`.unwrap()` now, refactor later" because later usually never comes.
157+
119158
### Dereferences
120159

121160
Rust will often admonish you to add an extra dereference (`*`) by comparing the expected input and actual types, and you'll need to write something like `.map(|elem| *elem * 2)` to correct your code. A tell tale sign of this is that the expected types and the actual type differ by the number of `&`'s present.
@@ -136,47 +175,46 @@ let z = x.iter().zip(y.iter())
136175

137176
where the `.map(|(a, b)| a + b)` is iterating over `[(10, 1), (20, 2), (30, 3)]` and calling the left argument `a` and the right argument `b`, in each iteration.
138177

139-
## Iterator chains workflow advice
140-
141-
Start every iterator call on a new line, so that you can see closure arguments and type hints for the iterator at the end of the line clearly.
142-
143-
When in doubt, write `.map(|x| x)` first to see what item types you get and decide on what iterator methods to use and what to do inside a closure based on that.
144-
145178
## Step-by-Step-Solution
146179

147-
⚠️ NOTICE! ⚠️
148-
149-
When starting out with iterators, it's very easy to be "led astray" by doing what is locally useful as suggested by the compiler.
150-
151-
Concretely, our first solution will feel like a slog because we'll deal with a lot of `Option` and `Result` wrapping and unwrapping that other languages wouldn't make explicit.
152-
153-
A second more idiomatic solution will emerge in `Step 6` once we learn a few key idioms from the standard library.
154-
155-
You, unfortunately, relive similar experiences when learning Rust without knowing the right tools from the standard library to handle errors elegantly.
156-
157-
🧘 END OF NOTICE 🧘
158-
159180
In general, we also recommend using the Rust documentation to get unstuck. In particular, look for the examples in the [Iterator](https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html) page of the standard library for this exercise.
160181

161182
If you ever feel completely stuck or that you haven’t understood something, please hail the trainers quickly.
162183

163184
### Step 1: New Project
164185

165-
Create a new binary Cargo project, check the build and see if it runs.
186+
Create a new binary Cargo project and run it.
166187

167188
Alternatively, use the [exercise-templates/iterators](../../exercise-templates/iterators/) template to get started.
168189
<details>
169190
<summary>Solution</summary>
170191

171192
```shell
172193
cargo new iterators
173-
cd iterators
194+
cd iterators
174195
cargo run
175196

176197
# if in exercise-book/exercise-templates/iterators
177198
cargo run --bin iterators1
178199
```
179200

201+
Place the string
202+
203+
```text
204+
//ignore everything that is not a number
205+
1
206+
2
207+
3
208+
4
209+
five
210+
6
211+
7
212+
213+
9
214+
X
215+
```
216+
217+
and place it in `iterators/numbers.txt`.
180218
</details>
181219

182220
### Step 2: Read the string data
@@ -188,6 +226,8 @@ Collect it into a string with `.collect::<String>()` and print it to verify you'
188226
<details>
189227
<summary>Solution</summary>
190228

229+
We'll get rid of the `.unwrap()` in the next section.
230+
191231
```rust
192232
#![allow(unused_imports)]
193233
use std::io::{BufRead, BufReader};
@@ -200,8 +240,9 @@ fn main() -> Result<(), Box<dyn Error>> {
200240
let reader = BufReader::new(f);
201241

202242
let file_lines = reader.lines()
203-
.map(|l| l.unwrap())
204-
.collect::<String>();
243+
.map(|l| l.unwrap())
244+
.collect::<String>();
245+
205246
println!("{:?}", file_lines);
206247

207248
Ok(())
@@ -210,7 +251,7 @@ fn main() -> Result<(), Box<dyn Error>> {
210251

211252
</details>
212253

213-
### Step 3: Filter for the numeric strings
254+
### Step 3: Skip the non-numeric lines
214255

215256
We'll collect into a `Vec<String>`s with [.parse()](https://doc.rust-lang.org/stable/std/primitive.str.html#method.parse) to show this intermediate step.
216257

@@ -219,6 +260,8 @@ Note that you may or may not need type annotations on `.parse()` depending on if
219260
<details>
220261
<summary>Solution</summary>
221262

263+
If the use of `filter_map` here is unfamiliar, go back and reread the ``Dealing with .unwrap()s in iterator chains`` section.
264+
222265
```rust
223266
#![allow(unused_imports)]
224267
use std::io::{BufRead, BufReader};
@@ -231,11 +274,10 @@ fn main() -> Result<(), Box<dyn Error>> {
231274
let reader = BufReader::new(f);
232275

233276
let numeric_lines = reader.lines()
234-
.map(|l| l.unwrap())
235-
.map(|s| s.parse::<i32>())
236-
.filter(|s| s.is_ok())
237-
.map(|l| l.unwrap().to_string())
238-
.collect::<Vec<String>>();
277+
.filter_map(|line| line.ok())
278+
.filter_map(|line| line.parse::<i32>().ok())
279+
.map(|stringy_num| stringy_num.to_string())
280+
.collect::<Vec<String>>();
239281
println!("{:?}", numeric_lines);
240282

241283
Ok(())
@@ -263,12 +305,11 @@ fn main() -> Result<(), Box<dyn Error>> {
263305
let reader = BufReader::new(f);
264306

265307
let odd_numbers = reader.lines()
266-
.map(|l| l.unwrap())
267-
.map(|s| s.parse())
268-
.filter(|s| s.is_ok())
269-
.map(|l| l.unwrap())
270-
.filter(|num| num % 2 != 0)
271-
.collect::<Vec<i32>>();
308+
.filter_map(|line| line.ok())
309+
.filter_map(|line| line.parse::<i32>().ok())
310+
.map(|stringy_num| stringy_num.to_string())
311+
.filter(|num| num % 2 != 0)
312+
.collect::<Vec<i32>>();
272313

273314
println!("{:?}", odd_numbers);
274315

@@ -299,50 +340,15 @@ fn main() -> Result<(), Box<dyn Error>> {
299340
let reader = BufReader::new(f);
300341

301342
let result = reader.lines()
302-
.map(|l| l.unwrap())
303-
.map(|s| s.parse())
304-
.filter(|s| s.is_ok())
305-
.map(|l| l.unwrap())
306-
.filter(|num| num % 2 != 0)
307-
.collect::<Vec<i32>>()
308-
.iter()
309-
.fold(0, |acc, elem| acc + elem);
310-
// Also works
311-
//.sum::<i32>();
312-
313-
println!("{:?}", result);
314-
315-
Ok(())
316-
}
317-
```
318-
319-
</details>
320-
321-
### Step 6: Idiomatic Rust
322-
323-
That first solution can be a *slog*.
324-
325-
Try writing a shorter solution using a [.filter_map()](https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html#method.filter_map).
326-
327-
<details>
328-
<summary>Solution</summary>
329-
330-
```rust
331-
#![allow(unused_imports)]
332-
use std::io::{BufRead, BufReader};
333-
use std::fs::File;
334-
use std::error::Error;
335-
336-
fn main() -> Result<(), Box<dyn Error>> {
337-
use crate::*;
338-
let f = File::open("../exercise-templates/iterators/numbers.txt")?;
339-
let reader = BufReader::new(f);
340-
341-
let result = reader.lines()
342-
.map(|l| l.unwrap())
343-
.filter_map(|s| s.parse().ok())
344-
.filter(|num| num % 2 != 0)
345-
.sum::<i32>();
343+
.filter_map(|line| line.ok())
344+
.filter_map(|line| line.parse::<i32>().ok())
345+
.map(|stringy_num| stringy_num.to_string())
346+
.filter(|num| num % 2 != 0)
347+
.collect::<Vec<i32>>()
348+
.iter()
349+
.fold(0, |acc, elem| acc + elem);
350+
// Also works
351+
//.sum::<i32>();
346352

347353
println!("{:?}", result);
348354

0 commit comments

Comments
 (0)