From 1d6ed92b36a956f66fa7ed372dc541fdcb5f436c Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Sat, 11 Dec 2021 15:38:34 +0100 Subject: [PATCH] 2021: day11: part 1 & 2 --- aoc2021/input/day11.txt | 10 ++ aoc2021/input/day11_provided.txt | 10 ++ aoc2021/src/day11.rs | 236 +++++++++++++++++++++++++++++++ aoc2021/src/lib.rs | 1 + aoc2021/src/main.rs | 2 + 5 files changed, 259 insertions(+) create mode 100644 aoc2021/input/day11.txt create mode 100644 aoc2021/input/day11_provided.txt create mode 100644 aoc2021/src/day11.rs diff --git a/aoc2021/input/day11.txt b/aoc2021/input/day11.txt new file mode 100644 index 0000000..3f9a2ec --- /dev/null +++ b/aoc2021/input/day11.txt @@ -0,0 +1,10 @@ +4764745784 +4643457176 +8322628477 +7617152546 +6137518165 +1556723176 +2187861886 +2553422625 +4817584638 +3754285662 diff --git a/aoc2021/input/day11_provided.txt b/aoc2021/input/day11_provided.txt new file mode 100644 index 0000000..03743f6 --- /dev/null +++ b/aoc2021/input/day11_provided.txt @@ -0,0 +1,10 @@ +5483143223 +2745854711 +5264556173 +6141336146 +6357385478 +4167524645 +2176841721 +6882881134 +4846848554 +5283751526 diff --git a/aoc2021/src/day11.rs b/aoc2021/src/day11.rs new file mode 100644 index 0000000..189032f --- /dev/null +++ b/aoc2021/src/day11.rs @@ -0,0 +1,236 @@ +use std::fmt::Write; + +use anyhow::{Context, Result}; + +const INPUT: &str = include_str!("../input/day11.txt"); + +pub fn run() -> Result { + let mut res = String::with_capacity(128); + + writeln!(res, "part 1: {}", part1(INPUT)?)?; + writeln!(res, "part 2: {}", part2(INPUT)?)?; + + Ok(res) +} + +fn part1(input: &str) -> Result { + let mut grid: OctopusGrid = input.parse()?; + + Ok((0..100).map(|_| grid.step()).sum()) +} + +fn part2(input: &str) -> Result { + let mut grid: OctopusGrid = input.parse()?; + + let (_, step) = (0..) + .map(|step| (grid.step(), step)) + .find(|(flashes, _)| *flashes == 100) + .context("never reached step where octopuses all flashed simultaneously")?; + + Ok(step + 1) +} + +#[derive(Clone, Copy, Debug)] +struct Octopus { + energy_level: u8, + has_flashed: bool, +} + +impl Octopus { + fn increment_energy_level(&mut self) { + self.energy_level += 1; + } + + fn reset_energy_level(&mut self) { + if self.has_flashed { + self.energy_level = 0; + self.has_flashed = false; + } + } + + fn flashes(&mut self) -> bool { + if self.energy_level > 9 && !self.has_flashed { + self.has_flashed = true; + return true; + } + + false + } +} + +impl TryFrom for Octopus { + type Error = anyhow::Error; + + fn try_from(value: char) -> Result { + Ok(Octopus { + energy_level: value + .to_digit(10) + .with_context(|| format!("couldn't convert char `{}` to digit", value))? + as u8, + has_flashed: false, + }) + } +} + +#[derive(Clone, Copy)] +enum Neighbour { + Up, + Down, + Left, + Right, + UpperLeft, + UpperRight, + LowerLeft, + LowerRight, +} + +impl Neighbour { + fn apply(&self, x: usize, y: usize, width: usize, height: usize) -> Option<(usize, usize)> { + match self { + Neighbour::Left if x > 0 => Some((x - 1, y)), + Neighbour::Right if x < width - 1 => Some((x + 1, y)), + Neighbour::Up if y > 0 => Some((x, y - 1)), + Neighbour::Down if y < height - 1 => Some((x, y + 1)), + Neighbour::UpperLeft => Neighbour::Left + .apply(x, y, width, height) + .and_then(|(x, y)| Neighbour::Up.apply(x, y, width, height)), + Neighbour::UpperRight => Neighbour::Right + .apply(x, y, width, height) + .and_then(|(x, y)| Neighbour::Up.apply(x, y, width, height)), + Neighbour::LowerLeft => Neighbour::Left + .apply(x, y, width, height) + .and_then(|(x, y)| Neighbour::Down.apply(x, y, width, height)), + Neighbour::LowerRight => Neighbour::Right + .apply(x, y, width, height) + .and_then(|(x, y)| Neighbour::Down.apply(x, y, width, height)), + _ => None, + } + } + + const ALL: &'static [Self] = &[ + Neighbour::Left, + Neighbour::Right, + Neighbour::Up, + Neighbour::Down, + Neighbour::UpperLeft, + Neighbour::UpperRight, + Neighbour::LowerLeft, + Neighbour::LowerRight, + ]; +} + +#[derive(Debug)] +struct OctopusGrid { + octopuses: Vec, + width: usize, + height: usize, +} + +impl OctopusGrid { + fn neighbours(&self, x: usize, y: usize) -> impl Iterator + 'static { + let width = self.width; + let height = self.height; + Neighbour::ALL + .iter() + .copied() + .filter_map(move |neighbour| neighbour.apply(x, y, width, height)) + } + + fn get_mut(&mut self, x: usize, y: usize) -> &mut Octopus { + &mut self.octopuses[y * self.width + x] + } + + fn step(&mut self) -> usize { + // First, the energy level of each octopus increases by 1. + self.octopuses + .iter_mut() + .for_each(Octopus::increment_energy_level); + + // Then, any octopus with an energy level greater than 9 flashes. This increases the energy + // level of all adjacent octopuses by 1, including octopuses that are diagonally adjacent. + // If this causes an octopus to have an energy level greater than 9, it also flashes. This + // process continues as long as new octopuses keep having their energy level increased + // beyond 9. (An octopus can only flash at most once per step.) + let mut flashed = true; + let mut flashes = 0; + while flashed { + flashed = false; + for index in 0..self.octopuses.len() { + if self.octopuses[index].flashes() { + let (x, y) = (index % self.width, index / self.width); + for (x, y) in self.neighbours(x, y) { + self.get_mut(x, y).increment_energy_level(); + } + + flashed = true; + flashes += 1; + } + } + } + + // Finally, any octopus that flashed during this step has its energy level set to 0, as it + // used all of its energy to flash. + self.octopuses + .iter_mut() + .for_each(Octopus::reset_energy_level); + + flashes + } +} + +impl std::str::FromStr for OctopusGrid { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + let mut octopuses = Vec::new(); + + let mut height = 0; + let mut width = None; + for line in s.lines().map(str::trim) { + let octopus_line = line + .chars() + .map(TryFrom::try_from) + .collect::>>()?; + + if width.is_none() { + width = Some(octopus_line.len()); + } + + height += 1; + octopuses.extend_from_slice(&octopus_line); + } + + Ok(OctopusGrid { + octopuses, + width: width.context("0 lines parsed, width never computed")?, + height, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const PROVIDED: &str = include_str!("../input/day11_provided.txt"); + + #[test] + fn part1_provided() { + assert_eq!(part1(PROVIDED).unwrap(), 1656); + } + + #[test] + fn part1_real() { + assert_eq!(part1(INPUT).unwrap(), 1588); + } + + #[test] + fn part2_provided() { + assert_eq!(part2(PROVIDED).unwrap(), 195); + } + + #[test] + fn part2_real() { + assert_eq!(part2(INPUT).unwrap(), 517); + } +} diff --git a/aoc2021/src/lib.rs b/aoc2021/src/lib.rs index cff51ba..6cbe7cd 100644 --- a/aoc2021/src/lib.rs +++ b/aoc2021/src/lib.rs @@ -10,3 +10,4 @@ pub mod day07; pub mod day08; pub mod day09; pub mod day10; +pub mod day11; diff --git a/aoc2021/src/main.rs b/aoc2021/src/main.rs index e45aa5c..e5145c4 100644 --- a/aoc2021/src/main.rs +++ b/aoc2021/src/main.rs @@ -12,6 +12,7 @@ use aoc2021::day07; use aoc2021::day08; use aoc2021::day09; use aoc2021::day10; +use aoc2021::day11; fn main() -> Result<()> { let days: &[DayFunc] = &[ @@ -25,6 +26,7 @@ fn main() -> Result<()> { day08::run, day09::run, day10::run, + day11::run, ]; aoc::run(days)