From 0416116a65fed1aec36ca08653e037d9396cc133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Christian=20Gr=C3=BCnhage?= Date: Fri, 10 Dec 2021 08:17:38 +0100 Subject: [PATCH] implement day 9 --- src/day_09/mod.rs | 63 ++++++++++++++++++++++++++ src/day_09/model.rs | 100 ++++++++++++++++++++++++++++++++++++++++++ src/day_09/parsing.rs | 37 ++++++++++++++++ src/lib.rs | 1 + 4 files changed, 201 insertions(+) create mode 100644 src/day_09/mod.rs create mode 100644 src/day_09/model.rs create mode 100644 src/day_09/parsing.rs diff --git a/src/day_09/mod.rs b/src/day_09/mod.rs new file mode 100644 index 0000000..0cc04b1 --- /dev/null +++ b/src/day_09/mod.rs @@ -0,0 +1,63 @@ +mod model; +mod parsing; + +use aoc_runner_derive::{aoc, aoc_generator}; +use itertools::Itertools; +pub use model::HeightMap; +pub use parsing::parse_map; +use yap::IntoTokens; + +#[aoc_generator(day9)] +pub fn parse_input(input: &str) -> HeightMap { + parse_map(&mut input.into_tokens()) +} + +#[aoc(day9, part1)] +pub fn part1(input: &HeightMap) -> usize { + input + .get_low_points() + .iter() + .map(|point| input.get_risk_level(*point)) + .flatten() + .sum() +} + +#[aoc(day9, part2)] +pub fn part2(input: &HeightMap) -> usize { + input + .get_basin_sizes() + .iter() + .sorted() + .rev() + .take(3) + .product() +} + +#[cfg(test)] +mod test { + const EXAMPLE_INPUT: &str = "2199943210 +3987894921 +9856789892 +8767896789 +9899965678"; + const RESULT_PART_1: usize = 15; + const RESULT_PART_2: usize = 1134; + + #[test] + fn part1_example() { + let result = super::part1(&super::parse_input(EXAMPLE_INPUT)); + assert_eq!(result, RESULT_PART_1); + } + + #[test] + fn part2_example() { + let result = super::part2(&super::parse_input(EXAMPLE_INPUT)); + assert_eq!(result, RESULT_PART_2); + } + + #[test] + fn parse_example() { + let _example = super::parse_input(EXAMPLE_INPUT); + //unimplemented!(); + } +} diff --git a/src/day_09/model.rs b/src/day_09/model.rs new file mode 100644 index 0000000..b251e60 --- /dev/null +++ b/src/day_09/model.rs @@ -0,0 +1,100 @@ +use itertools::Itertools; + +#[derive(Debug)] +pub struct HeightMap { + pub map: Vec>, + pub basin: Vec>>, +} + +impl HeightMap { + pub fn new(map: Vec>) -> Self { + let mut basin = Vec::new(); + for line in &map { + let mut basin_line = Vec::new(); + for _point in line { + basin_line.push(None) + } + basin.push(basin_line); + } + let mut new_self = Self { map, basin }; + let mut basin_counter = 0; + for y in 0..new_self.map.len() { + for x in 0..new_self.map[y].len() { + if new_self.map[y][x] < 9 && new_self.basin[y][x].is_none() { + new_self.flood_basin((x, y), basin_counter); + basin_counter += 1; + } + } + } + new_self + } + + pub fn get_low_points(&self) -> Vec<(isize, isize)> { + let mut low_points = Vec::new(); + for y in 0..self.map.len() { + for x in 0..self.map[y].len() { + if self.is_low_point((x as isize, y as isize)) { + low_points.push((x as isize, y as isize)) + } + } + } + low_points + } + + pub fn is_low_point(&self, position: (isize, isize)) -> bool { + let val = match self.get(position) { + Some(pos) => pos, + None => return false, + }; + let (x, y) = position; + for (x_off, y_off) in [(1isize, 0isize), (0, 1), (-1, 0), (0, -1)].iter() { + if let Some(neighbour) = self.get((x + x_off, y + y_off)) { + if neighbour <= val { + return false; + } + } + } + true + } + + pub fn flood_basin(&mut self, (x, y): (usize, usize), number: usize) { + if let Some(basin) = self.basin[y][x] { + assert_eq!(basin, number); + return; + } else { + self.basin[y][x] = Some(number) + } + for (x_off, y_off) in [(1isize, 0isize), (0, 1), (-1, 0), (0, -1)].iter() { + if let Some(neighbour) = self.get((x as isize + x_off, y as isize + y_off)) { + if neighbour < 9 { + self.flood_basin( + ((x as isize + x_off) as usize, (y as isize + y_off) as usize), + number, + ) + } + } + } + } + + pub fn get(&self, (x, y): (isize, isize)) -> Option { + let (x, y): (usize, usize) = match (x.try_into(), y.try_into()) { + (Ok(x), Ok(y)) => (x, y), + _ => return None, + }; + self.map.get(y).map(|val| val.get(x)).flatten().copied() + } + + pub fn get_risk_level(&self, position: (isize, isize)) -> Option { + self.get(position).map(|x| x + 1) + } + + pub fn get_basin_sizes(&self) -> Vec { + self.basin + .iter() + .flat_map(|line| line.iter()) + .filter_map(|val| *val) + .counts() + .into_values() + .collect() + } +} diff --git a/src/day_09/parsing.rs b/src/day_09/parsing.rs new file mode 100644 index 0000000..865746b --- /dev/null +++ b/src/day_09/parsing.rs @@ -0,0 +1,37 @@ +use yap::Tokens; + +use crate::parsing::{newline, parse_digit}; + +use super::HeightMap; + +pub fn parse_map(tokens: &mut impl Tokens) -> HeightMap { + let map: Vec> = tokens.sep_by(|t| parse_line(t), |t| newline(t)).collect(); + HeightMap::new(map) +} + +pub fn parse_line(tokens: &mut impl Tokens) -> Option> { + let digits: Vec = tokens.many(|t| parse_digit(t)).collect(); + if digits.is_empty() { + None + } else { + Some(digits) + } +} + +#[cfg(test)] +mod test { + use yap::IntoTokens; + + #[test] + fn parse_line() { + assert_eq!( + dbg!(super::parse_line(&mut "2199943210".into_tokens()).unwrap()).len(), + 10 + ) + } + + #[test] + fn parse_map() { + super::parse_map(&mut "01\n11".into_tokens()); + } +} diff --git a/src/lib.rs b/src/lib.rs index cd95fa2..3840967 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,5 +10,6 @@ pub mod day_05; pub mod day_06; pub mod day_07; pub mod day_08; +pub mod day_09; aoc_lib! { year = 2021 }