Add solution to day 9 part 2

master
Nick Krichevsky 2021-12-09 02:07:59 -05:00
parent 95a984fac4
commit e45adacf15
3 changed files with 122 additions and 37 deletions

16
day9/Cargo.lock generated
View File

@ -6,9 +6,25 @@ version = 3
name = "day9" name = "day9"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"itertools",
"thiserror", "thiserror",
] ]
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "itertools"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
dependencies = [
"either",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.33" version = "1.0.33"

View File

@ -7,3 +7,4 @@ edition = "2021"
[dependencies] [dependencies]
thiserror = "1.0" thiserror = "1.0"
itertools = "0.10"

View File

@ -1,4 +1,6 @@
#![warn(clippy::all, clippy::pedantic)] #![warn(clippy::all, clippy::pedantic)]
use itertools::Itertools;
use std::collections::{HashSet, VecDeque};
use std::env; use std::env;
use std::fs::File; use std::fs::File;
use std::io::{BufRead, BufReader}; use std::io::{BufRead, BufReader};
@ -10,55 +12,78 @@ enum Error {
RowOutOfRange(usize), RowOutOfRange(usize),
} }
fn find_low_point_indices( /// Get the indices (row, col) of all adjacent items that are in the input
fn get_adjacent_indices(
input: &[Vec<u32>], input: &[Vec<u32>],
depth: usize, (depth, col): (usize, usize),
) -> Result<impl Iterator<Item = usize> + '_, Error> { ) -> Result<Vec<(usize, usize)>, Error> {
let maybe_row = input.get(depth); let maybe_row = input.get(depth);
if maybe_row.is_none() { if maybe_row.is_none() {
return Err(Error::RowOutOfRange(depth)); return Err(Error::RowOutOfRange(depth));
} }
let row = maybe_row.unwrap(); let row = maybe_row.unwrap();
let iter = row.iter().enumerate().filter_map(move |(i, &item)| { let mut res = vec![];
let adjacent = {
let mut res = vec![
row.get(i + 1),
input.get(depth + 1).map(|below_row| {
below_row
.get(i)
.expect("below row does not match row length")
}),
];
if i > 0 { if col > 0 {
let left = row.get(i - 1); res.push((depth, col - 1));
res.push(left); }
}
if depth > 0 {
let above = input.get(depth - 1).map(|above_row| {
above_row
.get(i)
.expect("above row does not match row length")
});
res.push(above);
}
res if row.get(col + 1).is_some() {
}; res.push((depth, col + 1));
}
if adjacent let below = input.get(depth + 1).map(|below_row| {
.into_iter() below_row
.filter(Option::is_some) .get(col)
.all(|adjacent_item| item < *adjacent_item.unwrap()) .expect("below row does not match row length")
{
Some(i)
} else {
None
}
}); });
if below.is_some() {
res.push((depth + 1, col));
}
Ok(iter) let have_above_item = || {
input
.get(depth - 1)
.map(|above_row| {
above_row
.get(col)
.expect("above row does not match row length")
})
.is_some()
};
if depth > 0 && have_above_item() {
res.push((depth - 1, col));
}
Ok(res)
}
/// Get the indices of all low points in the input
fn find_low_point_indices(input: &[Vec<u32>], depth: usize) -> Result<Vec<usize>, Error> {
let maybe_row = input.get(depth);
if maybe_row.is_none() {
return Err(Error::RowOutOfRange(depth));
}
let row = maybe_row.unwrap();
let mut res = vec![];
for (i, &item) in row.iter().enumerate() {
let adjacent_res = get_adjacent_indices(input, (depth, i));
if let Err(err) = adjacent_res {
return Err(err);
}
if adjacent_res
.unwrap()
.into_iter()
.all(|(adjacent_depth, adjacent_col)| item < input[adjacent_depth][adjacent_col])
{
res.push(i);
}
}
Ok(res)
} }
fn part1(input: &[Vec<u32>]) -> u32 { fn part1(input: &[Vec<u32>]) -> u32 {
@ -68,12 +93,54 @@ fn part1(input: &[Vec<u32>]) -> u32 {
.map(|(depth, row)| { .map(|(depth, row)| {
find_low_point_indices(input, depth) find_low_point_indices(input, depth)
.expect("failed to get row for depth analysis") .expect("failed to get row for depth analysis")
.into_iter()
.map(|idx| row[idx] + 1) .map(|idx| row[idx] + 1)
.sum::<u32>() .sum::<u32>()
}) })
.sum() .sum()
} }
fn part2(input: &[Vec<u32>]) -> u32 {
let basin_sizes = input.iter().enumerate().flat_map(|(depth, _)| {
let low_points =
find_low_point_indices(input, depth).expect("failed to get row for depth analysis");
low_points.into_iter().map(move |low_point_idx| {
let mut to_visit = [(depth, low_point_idx)]
.into_iter()
.collect::<VecDeque<_>>();
let mut visited = HashSet::<(usize, usize)>::new();
// 1 includes the low point
let mut num_in_basin = 1;
// Flood the board, terminating our search once we hit a nine
while !to_visit.is_empty() {
let (visiting_row, visiting_col) = to_visit.pop_front().unwrap();
let visiting = input[visiting_row][visiting_col];
let adjacent_iter = get_adjacent_indices(input, (visiting_row, visiting_col))
.expect("failed to get adjacent items for bfs");
for (adjacent_row, adjacent_col) in adjacent_iter {
if visited.contains(&(adjacent_row, adjacent_col)) {
continue;
}
let adjacent = input[adjacent_row][adjacent_col];
// flows from high to low, nine can't be part of the basin
if adjacent > visiting && adjacent != 9 {
num_in_basin += 1;
to_visit.push_back((adjacent_row, adjacent_col));
visited.insert((adjacent_row, adjacent_col));
}
}
}
num_in_basin
})
});
basin_sizes.sorted().rev().take(3).product()
}
fn main() { fn main() {
let input_file_name = env::args().nth(1).expect("No input filename specified"); let input_file_name = env::args().nth(1).expect("No input filename specified");
let input_file = File::open(input_file_name).expect("Could not open input file"); let input_file = File::open(input_file_name).expect("Could not open input file");
@ -91,4 +158,5 @@ fn main() {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
println!("Part 1: {}", part1(&input_lines)); println!("Part 1: {}", part1(&input_lines));
println!("Part 2: {}", part2(&input_lines));
} }