From 13b662060f70736406acf1c20f08ac52adafbff9 Mon Sep 17 00:00:00 2001 From: Burnus Date: Sun, 2 Apr 2023 10:42:39 +0200 Subject: [PATCH] Added Solution for 2019 day 24 --- 2019/day24_planet_of_discord/Cargo.toml | 8 + 2019/day24_planet_of_discord/challenge.txt | 254 +++++++++++++ 2019/day24_planet_of_discord/src/lib.rs | 337 ++++++++++++++++++ .../tests/challenge_input | 5 + .../tests/sample_input | 5 + 5 files changed, 609 insertions(+) create mode 100644 2019/day24_planet_of_discord/Cargo.toml create mode 100644 2019/day24_planet_of_discord/challenge.txt create mode 100644 2019/day24_planet_of_discord/src/lib.rs create mode 100644 2019/day24_planet_of_discord/tests/challenge_input create mode 100644 2019/day24_planet_of_discord/tests/sample_input diff --git a/2019/day24_planet_of_discord/Cargo.toml b/2019/day24_planet_of_discord/Cargo.toml new file mode 100644 index 0000000..691a610 --- /dev/null +++ b/2019/day24_planet_of_discord/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "day24_planet_of_discord" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/2019/day24_planet_of_discord/challenge.txt b/2019/day24_planet_of_discord/challenge.txt new file mode 100644 index 0000000..3448890 --- /dev/null +++ b/2019/day24_planet_of_discord/challenge.txt @@ -0,0 +1,254 @@ +You land on [Eris](https://en.wikipedia.org/wiki/Eris_(dwarf_planet)), your last stop before reaching Santa. As soon as you do, your sensors start picking up strange life forms moving around: Eris is infested with [bugs](https://www.nationalgeographic.org/thisday/sep9/worlds-first-computer-bug/)! With an over 24-hour roundtrip for messages between you and Earth, you'll have to deal with this problem on your own. + +Eris isn't a very large place; a scan of the entire area fits into a 5x5 grid (your puzzle input). The scan shows *bugs* (`#`) and *empty spaces* (`.`). + +Each *minute*, The bugs live and die based on the number of bugs in the *four adjacent tiles*: + +* A bug *dies* (becoming an empty space) unless there is *exactly one* bug adjacent to it. +* An empty space *becomes infested* with a bug if *exactly one or two* bugs are adjacent to it. + +Otherwise, a bug or empty space remains the same. (Tiles on the edges of the grid have fewer than four adjacent tiles; the missing tiles count as empty space.) This process happens in every location *simultaneously*; that is, within the same minute, the number of adjacent bugs is counted for every tile first, and then the tiles are updated. + +Here are the first few minutes of an example scenario: + +``` +Initial state: +....# +#..#. +#..## +..#.. +#.... + +After 1 minute: +#..#. +####. +###.# +##.## +.##.. + +After 2 minutes: +##### +....# +....# +...#. +#.### + +After 3 minutes: +#.... +####. +...## +#.##. +.##.# + +After 4 minutes: +####. +....# +##..# +..... +##... + +``` + +To understand the nature of the bugs, watch for the first time a layout of bugs and empty spaces *matches any previous layout*. In the example above, the first layout to appear twice is: + +``` +..... +..... +..... +#.... +.#... + +``` + +To calculate the *biodiversity rating* for this layout, consider each tile left-to-right in the top row, then left-to-right in the second row, and so on. Each of these tiles is worth biodiversity points equal to *increasing powers of two*: 1, 2, 4, 8, 16, 32, and so on. Add up the biodiversity points for tiles with bugs; in this example, the 16th tile (`32768` points) and 22nd tile (`2097152` points) have bugs, a total biodiversity rating of `*2129920*`. + +*What is the biodiversity rating for the first layout that appears twice?* + +Your puzzle answer was `28781019`. + +\--- Part Two --- +---------- + +After careful analysis, one thing is certain: *you have no idea where all these bugs are coming from*. + +Then, you remember: Eris is an old [Plutonian](20) settlement! Clearly, the bugs are coming from recursively-folded space. + +This 5x5 grid is *only one* level in an *infinite* number of recursion levels. The tile in the middle of the grid is actually another 5x5 grid, the grid in your scan is contained as the middle tile of a larger 5x5 grid, and so on. Two levels of grids look like this: + +``` + | | | | + | | | | + | | | | +-----+-----+---------+-----+----- + | | | | + | | | | + | | | | +-----+-----+---------+-----+----- + | | | | | | | | + | |-+-+-+-+-| | + | | | | | | | | + | |-+-+-+-+-| | + | | | |?| | | | + | |-+-+-+-+-| | + | | | | | | | | + | |-+-+-+-+-| | + | | | | | | | | +-----+-----+---------+-----+----- + | | | | + | | | | + | | | | +-----+-----+---------+-----+----- + | | | | + | | | | + | | | | + +``` + +(To save space, some of the tiles are not drawn to scale.) Remember, this is only a small part of the infinitely recursive grid; there is a 5x5 grid that contains this diagram, and a 5x5 grid that contains that one, and so on. Also, the `?` in the diagram contains another 5x5 grid, which itself contains another 5x5 grid, and so on. + +The scan you took (your puzzle input) shows where the bugs are *on a single level* of this structure. The middle tile of your scan is empty to accommodate the recursive grids within it. Initially, no other levels contain bugs. + +Tiles still count as *adjacent* if they are directly *up, down, left, or right* of a given tile. Some tiles have adjacent tiles at a recursion level above or below its own level. For example: + +``` + | | | | + 1 | 2 | 3 | 4 | 5 + | | | | +-----+-----+---------+-----+----- + | | | | + 6 | 7 | 8 | 9 | 10 + | | | | +-----+-----+---------+-----+----- + | |A|B|C|D|E| | + | |-+-+-+-+-| | + | |F|G|H|I|J| | + | |-+-+-+-+-| | + 11 | 12 |K|L|?|N|O| 14 | 15 + | |-+-+-+-+-| | + | |P|Q|R|S|T| | + | |-+-+-+-+-| | + | |U|V|W|X|Y| | +-----+-----+---------+-----+----- + | | | | + 16 | 17 | 18 | 19 | 20 + | | | | +-----+-----+---------+-----+----- + | | | | + 21 | 22 | 23 | 24 | 25 + | | | | + +``` + +* Tile 19 has four adjacent tiles: 14, 18, 20, and 24. +* Tile G has four adjacent tiles: B, F, H, and L. +* Tile D has four adjacent tiles: 8, C, E, and I. +* Tile E has four adjacent tiles: 8, D, 14, and J. +* Tile 14 has *eight* adjacent tiles: 9, E, J, O, T, Y, 15, and 19. +* Tile N has *eight* adjacent tiles: I, O, S, and five tiles within the sub-grid marked `?`. + +The rules about bugs living and dying are the same as before. + +For example, consider the same initial state as above: + +``` +....# +#..#. +#.?## +..#.. +#.... + +``` + +The center tile is drawn as `?` to indicate the next recursive grid. Call this level 0; the grid within this one is level 1, and the grid that contains this one is level -1. Then, after *ten* minutes, the grid at each level would look like this: + +``` +Depth -5: +..#.. +.#.#. +..?.# +.#.#. +..#.. + +Depth -4: +...#. +...## +..?.. +...## +...#. + +Depth -3: +#.#.. +.#... +..?.. +.#... +#.#.. + +Depth -2: +.#.## +....# +..?.# +...## +.###. + +Depth -1: +#..## +...## +..?.. +...#. +.#### + +Depth 0: +.#... +.#.## +.#?.. +..... +..... + +Depth 1: +.##.. +#..## +..?.# +##.## +##### + +Depth 2: +###.. +##.#. +#.?.. +.#.## +#.#.. + +Depth 3: +..### +..... +#.?.. +#.... +#...# + +Depth 4: +.###. +#..#. +#.?.. +##.#. +..... + +Depth 5: +####. +#..#. +#.?#. +####. +..... + +``` + +In this example, after 10 minutes, a total of `*99*` bugs are present. + +Starting with your scan, *how many bugs are present after 200 minutes?* + +Your puzzle answer was `1939`. + +Both parts of this puzzle are complete! They provide two gold stars: \*\* + +At this point, you should [return to your Advent calendar](/2019) and try another puzzle. + +If you still want to see it, you can [get your puzzle input](24/input). \ No newline at end of file diff --git a/2019/day24_planet_of_discord/src/lib.rs b/2019/day24_planet_of_discord/src/lib.rs new file mode 100644 index 0000000..1c8b60d --- /dev/null +++ b/2019/day24_planet_of_discord/src/lib.rs @@ -0,0 +1,337 @@ +use core::fmt::Display; +use std::collections::HashSet; + +#[derive(Debug, PartialEq, Eq)] +pub enum ParseError { + LineMalformed(String), +} + +impl Display for ParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::LineMalformed(v) => write!(f, "Line is malformed: {v}"), + } + } +} + +#[derive(PartialEq, Clone, Copy)] +enum BugState { + Bug, + Empty, +} + +impl BugState { + fn other(&self) -> Self { + match self { + Self::Bug => Self::Empty, + Self::Empty => Self::Bug, + } + } +} + +#[derive(Clone, Copy)] +struct Grid { + bugs: [[BugState; 5]; 5], +} + +impl TryFrom<&str> for Grid { + type Error = ParseError; + + fn try_from(value: &str) -> Result { + let mut bugs = [[BugState::Empty; 5]; 5]; + for (y, row) in value.lines().enumerate() { + for (x, c) in row.chars().enumerate() { + match c { + '.' => (), + '#' => bugs[y][x] = BugState::Bug, + _ => return Err(ParseError::LineMalformed(format!("Unexpected token: {c}"))), + } + } + } + Ok(Self { bugs }) + } +} + +impl Grid { + fn empty() -> Self { + Self { bugs: [[BugState::Empty; 5]; 5] } + } + + fn bugs_adjacent_to(&self, (x, y): (usize, usize)) -> usize { + [(1, 0), (0, 1), (2, 1), (1, 2)].iter().filter(|(dx, dy)| (1..=5).contains(&(x+dx)) && (1..=5).contains(&(y+dy)) && self.bugs[y+dy-1][x+dx-1] == BugState::Bug).count() + } + + fn next_minute(&mut self) { + let mut next = self.bugs; + for y in 0..5 { + for x in 0..5 { + next[y][x] = match self.bugs_adjacent_to((x, y)) { + 1 => BugState::Bug, + 2 => self.bugs[y][x].other(), + _ => BugState::Empty, + } + } + } + std::mem::swap(&mut next, &mut self.bugs); + } + + fn biodiversity_rating(&self) -> usize { + self.bugs.iter().enumerate().map(|(y, row)| row.iter().enumerate().map(|(x, state)| if state == &BugState::Bug { 2_usize.pow(5*y as u32 + x as u32) } else { 0 }).sum::()).sum() + } + + fn print(&self) -> String { + self.bugs.iter().map(|row| row.iter().map(|state| if state == &BugState::Bug { '#' } else { '.' }).chain(['\n'].into_iter()).collect::()).collect() + } +} + +struct Grid3D { + grids: [Grid; 401], +} + +impl TryFrom<&str> for Grid3D { + type Error = ParseError; + fn try_from(value: &str) -> Result { + let mut grids = [Grid::empty(); 401]; + grids[200] = Grid::try_from(value)?; + Ok(Self { grids, }) + } +} + +impl Grid3D { + fn bugs_adjacent_to(&self, (x, y, z): (usize, usize, usize)) -> usize { + match (x, y, z) { + (2, 2, _) => 0, + (0, 0, 0) => [(0, 1, 0), (1, 0, 0)].iter().filter(|(x, y, z)| self.grids[*z].bugs[*y][*x] == BugState::Bug).count(), + (0, 0, z) => [(0, 1, z), (1, 0, z), (2, 1, z-1), (1, 2, z-1)].iter().filter(|(x, y, z)| self.grids[*z].bugs[*y][*x] == BugState::Bug).count(), + (0, 4, 0) => [(0, 3, 0), (1, 4, 0)].iter().filter(|(x, y, z)| self.grids[*z].bugs[*y][*x] == BugState::Bug).count(), + (0, 4, z) => [(0, 3, z), (1, 4, z), (1, 2, z-1), (2, 3, z-1)].iter().filter(|(x, y, z)| self.grids[*z].bugs[*y][*x] == BugState::Bug).count(), + (0, y, 0) => [(0, y-1, 0), (1, y, 0), (0, y+1, 0)].iter().filter(|(x, y, z)| self.grids[*z].bugs[*y][*x] == BugState::Bug).count(), + (0, y, z) => [(0, y-1, z), (1, y, z), (0, y+1, z), (1, 2, z-1)].iter().filter(|(x, y, z)| self.grids[*z].bugs[*y][*x] == BugState::Bug).count(), + (4, 0, 0) => [(3, 0, 0), (4, 1, 0)].iter().filter(|(x, y, z)| self.grids[*z].bugs[*y][*x] == BugState::Bug).count(), + (4, 0, z) => [(3, 0, z), (4, 1, z), (2, 1, z-1), (3, 2, z-1)].iter().filter(|(x, y, z)| self.grids[*z].bugs[*y][*x] == BugState::Bug).count(), + (4, 4, 0) => [(4, 3, 0), (3, 4, 0)].iter().filter(|(x, y, z)| self.grids[*z].bugs[*y][*x] == BugState::Bug).count(), + (4, 4, z) => [(4, 3, z), (3, 4, z), (2, 3, z-1), (3, 2, z-1)].iter().filter(|(x, y, z)| self.grids[*z].bugs[*y][*x] == BugState::Bug).count(), + (4, y, 0) => [(4, y-1, 0), (3, y, 0), (4, y+1, 0)].iter().filter(|(x, y, z)| self.grids[*z].bugs[*y][*x] == BugState::Bug).count(), + (4, y, z) => [(4, y-1, z), (3, y, z), (4, y+1, z), (3, 2, z-1)].iter().filter(|(x, y, z)| self.grids[*z].bugs[*y][*x] == BugState::Bug).count(), + (x, 0, 0) => [(x-1, 0, 0), (x, 1, 0), (x+1, 0, 0)].iter().filter(|(x, y, z)| self.grids[*z].bugs[*y][*x] == BugState::Bug).count(), + (x, 0, z) => [(x-1, 0, z), (x, 1, z), (x+1, 0, z), (2, 1, z-1)].iter().filter(|(x, y, z)| self.grids[*z].bugs[*y][*x] == BugState::Bug).count(), + (x, 4, 0) => [(x-1, 4, 0), (x, 3, 0), (x+1, 4, 0)].iter().filter(|(x, y, z)| self.grids[*z].bugs[*y][*x] == BugState::Bug).count(), + (x, 4, z) => [(x-1, 4, z), (x, 3, z), (x+1, 4, z), (2, 3, z-1)].iter().filter(|(x, y, z)| self.grids[*z].bugs[*y][*x] == BugState::Bug).count(), + (1, 2, 400) => [(1, 1, 400), (0, 2, 400), (1, 3, 400)].iter().filter(|(x, y, z)| self.grids[*z].bugs[*y][*x] == BugState::Bug).count(), + (2, 1, 400) => [(1, 1, 400), (2, 0, 400), (3, 1, 400)].iter().filter(|(x, y, z)| self.grids[*z].bugs[*y][*x] == BugState::Bug).count(), + (2, 3, 400) => [(1, 3, 400), (2, 4, 400), (3, 3, 400)].iter().filter(|(x, y, z)| self.grids[*z].bugs[*y][*x] == BugState::Bug).count(), + (3, 2, 400) => [(3, 1, 400), (4, 2, 400), (3, 3, 400)].iter().filter(|(x, y, z)| self.grids[*z].bugs[*y][*x] == BugState::Bug).count(), + (1, 2, z) => [(1, 1, z), (0, 2, z), (1, 3, z), (0, 0, z+1), (0, 1, z+1), (0, 2, z+1), (0, 3, z+1), (0, 4, z+1)].iter().filter(|(x, y, z)| self.grids[*z].bugs[*y][*x] == BugState::Bug).count(), + (2, 1, z) => [(1, 1, z), (2, 0, z), (3, 1, z), (0, 0, z+1), (1, 0, z+1), (2, 0, z+1), (3, 0, z+1), (4, 0, z+1)].iter().filter(|(x, y, z)| self.grids[*z].bugs[*y][*x] == BugState::Bug).count(), + (2, 3, z) => [(1, 3, z), (2, 4, z), (3, 3, z), (0, 4, z+1), (1, 4, z+1), (2, 4, z+1), (3, 4, z+1), (4, 4, z+1)].iter().filter(|(x, y, z)| self.grids[*z].bugs[*y][*x] == BugState::Bug).count(), + (3, 2, z) => [(3, 1, z), (4, 2, z), (3, 3, z), (4, 0, z+1), (4, 1, z+1), (4, 2, z+1), (4, 3, z+1), (4, 4, z+1)].iter().filter(|(x, y, z)| self.grids[*z].bugs[*y][*x] == BugState::Bug).count(), + (x, y, z) => [(x-1, y, z), (x, y-1, z), (x+1, y, z), (x, y+1, z)].iter().filter(|(x, y, z)| self.grids[*z].bugs[*y][*x] == BugState::Bug).count(), + } + } + + fn next_minute(&mut self) { + let mut next = self.grids; + for z in 0..401 { + for y in 0..5 { + for x in 0..5 { + next[z].bugs[y][x] = match self.bugs_adjacent_to((x, y, z)) { + 1 => BugState::Bug, + 2 => self.grids[z].bugs[y][x].other(), + _ => BugState::Empty, + } + } + } + } + std::mem::swap(&mut next, &mut self.grids); + } +} + +pub fn run_1(input: &str) -> Result { + let mut grid = Grid::try_from(input)?; + let mut previous = HashSet::from([grid.biodiversity_rating()]); + loop { + grid.next_minute(); + let this = grid.biodiversity_rating(); + if previous.contains(&this) { + return Ok(this); + } + previous.insert(this); + } +} + +pub fn run_2(input: &str, turns: usize) -> Result { + let mut grid = Grid3D::try_from(input)?; + for _ in 0..turns { + grid.next_minute(); + } + // for level in 195..=205 { + // println!("{}", grid.grids[level].print()); + // } + Ok(grid.grids.iter().map(|level| level.bugs.iter().map(|row| row.iter().filter(|state| state == &&BugState::Bug).count()).sum::()).sum()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs::read_to_string; + + fn read_file(name: &str) -> String { + read_to_string(name).expect(&format!("Unable to read file: {name}")[..]).trim().to_string() + } + + #[test] + fn biodiversity_rating_sample() { + let mut grid = Grid{ bugs: [[BugState::Empty; 5]; 5], }; + grid.bugs[3][0] = BugState::Bug; + grid.bugs[4][1] = BugState::Bug; + assert_eq!(grid.biodiversity_rating(), 2129920); + } + + #[test] + fn test_sample() { + let sample_input = read_file("tests/sample_input"); + let mut grid = Grid::try_from(&sample_input[..]).unwrap(); + let expected_states = [ +"....# +#..#. +#..## +..#.. +#.... +", +"#..#. +####. +###.# +##.## +.##.. +", +"##### +....# +....# +...#. +#.### +", +"#.... +####. +...## +#.##. +.##.# +", +"####. +....# +##..# +..... +##... +", +]; + for expected in expected_states { + let actual = grid.print(); + assert_eq!(&actual[..], expected); + grid.next_minute(); + } + + assert_eq!(run_2(&sample_input, 10), Ok(99)) + // assert_eq!(run(&sample_input), Ok((0, 0))); + } + + #[test] + fn test_recursive_next() { + let initial = +"....# +#..#. +#..## +..#.. +#...."; + let mut grid = Grid3D::try_from(initial).unwrap(); + let expected_1 = [ +"..... +..#.. +...#. +..#.. +..... +", +"#..#. +####. +##..# +##.## +.##.. +", +"....# +....# +....# +....# +##### +", +]; + let expected_2 = [ +"..#.. +.#.#. +....# +.#.#. +..#.. +", +"..... +..... +..... +...#. +..... +", +"####. +#..#. +#..#. +####. +..... +", +]; + let expected_3 = [ +"..... +..#.. +...#. +..#.. +..... +", + +".#.#. +#...# +.#... +#...# +.#.#. +", + +"..... +..... +...#. +..#.# +...#. +", + +"....# +.##.# +.#..# +....# +####. +", + +]; + grid.next_minute(); + for (level, bugs) in expected_1.iter().enumerate() { + assert_eq!(grid.grids[199+level].print(), bugs.to_string()); + } + + grid.next_minute(); + for (level, bugs) in expected_2.iter().enumerate() { + assert_eq!(grid.grids[199+level].print(), bugs.to_string()); + } + + grid.next_minute(); + for (level, bugs) in expected_3.iter().enumerate() { + assert_eq!(grid.grids[198+level].print(), bugs.to_string()); + } + } + + #[test] + fn test_challenge() { + let challenge_input = read_file("tests/challenge_input"); + assert_eq!(run_1(&challenge_input), Ok(28781019)); + assert_eq!(run_2(&challenge_input, 200), Ok(1939)); + } +} diff --git a/2019/day24_planet_of_discord/tests/challenge_input b/2019/day24_planet_of_discord/tests/challenge_input new file mode 100644 index 0000000..ba900ad --- /dev/null +++ b/2019/day24_planet_of_discord/tests/challenge_input @@ -0,0 +1,5 @@ +##.#. +.##.. +##.#. +.#### +###.. diff --git a/2019/day24_planet_of_discord/tests/sample_input b/2019/day24_planet_of_discord/tests/sample_input new file mode 100644 index 0000000..704a112 --- /dev/null +++ b/2019/day24_planet_of_discord/tests/sample_input @@ -0,0 +1,5 @@ +....# +#..#. +#..## +..#.. +#....