diff --git a/rust/main.rs b/rust/main.rs index 82e857e..3b4b96d 100644 --- a/rust/main.rs +++ b/rust/main.rs @@ -4,9 +4,10 @@ use crate::puzzle::Puzzle; use puzzles::day1; use puzzles::day2; use puzzles::day3; +use puzzles::day4; use std::fs; -const PUZZLES: [&dyn SomePuzzle; 3] = [&day1::PUZZLE, &day2::PUZZLE, &day3::PUZZLE]; +const PUZZLES: [&dyn SomePuzzle; 4] = [&day1::PUZZLE, &day2::PUZZLE, &day3::PUZZLE, &day4::PUZZLE]; fn main() { [false, true].iter().for_each(|is_real_data| { diff --git a/rust/puzzles/day4.rs b/rust/puzzles/day4.rs new file mode 100644 index 0000000..91fbe1d --- /dev/null +++ b/rust/puzzles/day4.rs @@ -0,0 +1,129 @@ +use crate::puzzle::Puzzle; +use nom::{ + Parser, + branch::alt, + character::complete::{char, newline}, + combinator::{eof, value}, + error::Error, + multi::{many1, separated_list1}, + sequence::terminated, +}; + +pub const PUZZLE: Puzzle>, 2> = Puzzle { + number: 4, + parser: |input| { + terminated::<_, _, Error<&str>, _, _>( + terminated( + separated_list1( + newline, + many1(alt(( + value(InTile::Empty, char('.')), + value(InTile::Roll, char('@')), + ))), + ), + newline, + ), + eof, + ) + .parse(input) + .unwrap() + .1 + }, + parts: [ + |input| { + let initial_rolls = count_rolls(&input); + let final_rolls = count_rolls(&remove_accessible_rolls(&find_accessible(&input))); + (initial_rolls - final_rolls).to_string() + }, + |input| { + let mut grid = input.clone(); + let initial_rolls = count_rolls(&grid); + loop { + let out_grid = find_accessible(&grid); + if none_accessible(&out_grid) { + break; + } + grid = remove_accessible_rolls(&out_grid); + } + let final_rolls = count_rolls(&grid); + (initial_rolls - final_rolls).to_string() + }, + ], +}; + +#[derive(Clone, PartialEq, Eq)] +pub enum InTile { + Empty, + Roll, +} + +#[derive(Clone, PartialEq, Eq)] +enum OutTile { + Empty, + Roll, + Accessible, +} + +fn find_accessible(grid: &Vec>) -> Vec> { + grid.iter() + .enumerate() + .map(|(y, row)| { + row.iter() + .enumerate() + .map(|(x, t)| match t { + InTile::Empty => OutTile::Empty, + InTile::Roll => { + let mut neighbor_rolls = 0; + for dx in -1..=1 { + for dy in -1..=1 { + if dx == 0 && dy == 0 { + continue; + } + if grid + .get(y.wrapping_add_signed(dy)) + .and_then(|r| r.get(x.wrapping_add_signed(dx))) + == Some(&InTile::Roll) + { + neighbor_rolls += 1; + } + } + } + if neighbor_rolls < 4 { + OutTile::Accessible + } else { + OutTile::Roll + } + } + }) + .collect() + }) + .collect() +} + +fn remove_accessible_rolls(grid: &Vec>) -> Vec> { + grid.iter() + .map(|r| { + r.iter() + .map(|t| { + (|tile| match tile { + OutTile::Empty => InTile::Empty, + OutTile::Roll => InTile::Roll, + OutTile::Accessible => InTile::Empty, + })(t.clone()) + }) + .collect() + }) + .collect() +} + +fn none_accessible(grid: &Vec>) -> bool { + !grid + .iter() + .any(|row| row.iter().any(|t| *t == OutTile::Accessible)) +} + +fn count_rolls(grid: &Vec>) -> usize { + grid.iter() + .map(|row| row.iter().filter(|t| **t == InTile::Roll).count()) + .sum() +} diff --git a/rust/puzzles/mod.rs b/rust/puzzles/mod.rs index 96606ab..b6dc6f5 100644 --- a/rust/puzzles/mod.rs +++ b/rust/puzzles/mod.rs @@ -1,3 +1,4 @@ pub(crate) mod day1; pub(crate) mod day2; pub(crate) mod day3; +pub(crate) mod day4;