️🎄 Advent of Code 2021, Day 9
Smoke Basin
2022-08-28
In which I decide to stop messing around and do things Properly™ when it comes to fallible parsing.
Day 9: Smoke Basin
The input this time around—as shown in the example—is a grid of integers:
2199943210
3987894921
9856789892
8767896789
9899965678
Parsing the input into a Vec<Vec<usize>>
was easy enough:
fn get_lava_tubes(input: &str) -> Vec<Vec<usize>> {
input
.trim()
.lines()
.map(|line| {
line.chars()
.map(|c| c.to_digit(10).unwrap() as usize)
.collect::<Vec<_>>()
})
.collect()
}
However, in retrospect there's one thing I really need to improve: my use of
unwrap()
everywhere.
The problem is that c.to_digit(10)
: I should, of course, return a
Result
in the event that it fails to parse a character.
Removing the unwrap()
and and leaving the rest as-is leads to
Vec<Option<u32>>
. Rust, however, has a nice trick to invert that. If
instead, I explicitly specify the type:
line.chars()
.map(|c| c.to_digit(10))
.collect::<Option<Vec<_>>>()
…then Vec<Option<T>>
becomes Option<Vec<T>>
. Using the same
feature on the outer collect()
, converts what would have been a
Vec<Option<Vec<T>>>
to a Option<Vec<Vec<T>>>
.
That resulting Option
can then be converted into the desired
Result
with ok_or()
:
fn get_lava_tubes(input: &str) -> Result<Vec<Vec<u32>>, ()> {
input
.trim()
.lines()
.map(|line| {
line.chars()
.map(|c| c.to_digit(10))
.collect::<Option<Vec<_>>>()
})
.collect::<Option<Vec<_>>>()
.ok_or(())
}
Skipping over my actual implementation (a breadth-first-search which is arguably less interesting than getting better at the above), the existing solution no longer works.
My standard layout—actually implemented as a Cookiecutter template—for each day of Advent of Code is something like this:
❯ tree .
.
├── Cargo.lock
├── Cargo.toml
├── input.txt
└── src
├── lib.rs
└── main.rs
1 directory, 5 files
That is, most of the implementation is in lib.rs
, with main.rs
doing little
more than provide an entry point. The main.rs
for this day, for instance,
looks something like:
use std::fs;
use ::day09::*;
fn main() {
let input = fs::read_to_string("input.txt").expect("Error reading input.txt");
println!(
"What is the sum of the risk levels of all low points on your heightmap? {}",
get_part_one(&input),
);
println!(
"What do you get if you multiply together the sizes of the three largest basins? {}",
get_part_two(&input),
);
}
However, now that we're more correctly returning a Result
due to our
fallible parsing, this won't work: we'll try to print said Result
instead of the answer.
So a correct implementation would be:
fn main() -> Result<(), ()> {
let input = fs::read_to_string("input.txt").expect("Error reading input.txt");
println!(
"What is the sum of the risk levels of all low points on your heightmap? {}",
get_part_one(&input)?,
);
println!(
"What do you get if you multiply together the sizes of the three largest basins? {}",
get_part_two(&input)?,
);
Ok(())
}
As documented in this from the Rust 1.61 release notes, it's now possible to specify the exact exit code, based on the return type.
That's one for another day.