diff --git a/src/day20.rs b/src/day20.rs index 20dd3df..95db47b 100644 --- a/src/day20.rs +++ b/src/day20.rs @@ -1,5 +1,4 @@ use std::collections::{HashMap, VecDeque}; - use aoc_runner_derive::{aoc, aoc_generator}; use num::Integer; diff --git a/src/day21.rs b/src/day21.rs new file mode 100644 index 0000000..0f9e5fb --- /dev/null +++ b/src/day21.rs @@ -0,0 +1,186 @@ +use std::collections::HashSet; +use aoc_runner_derive::{aoc, aoc_generator}; + +const STEPS: i32 = 64; + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +struct Position(i32, i32); + +#[derive(Debug, PartialEq)] +struct Garden { + rocks: HashSet, + start: Position, + extents: (Position, Position), +} + +#[aoc_generator(day21, part1)] +fn parse(input: &str) -> Garden { + let mut rocks: HashSet = HashSet::new(); + let mut start: Position = Position(0, 0); + for (row, line) in input.lines().enumerate() { + for (col, ch) in line.chars().enumerate() { + match ch { + '#' => { + rocks.insert(Position(row as i32, col as i32)); + } + 'S' => { + start = Position(row as i32, col as i32); + } + _ => {} + }; + } + } + let mut trans_rocks: HashSet = HashSet::new(); + for rock in rocks.iter() { + let trans_rock = Position(rock.0 - start.0, start.1 - rock.1); + trans_rocks.insert(trans_rock); + } + rocks = trans_rocks; + let num_rows = input.lines().count() as i32; + let num_cols = input.lines().next().unwrap().chars().count() as i32; + let extents = ( + Position(-num_rows / 2, -num_cols / 2), + Position(num_rows / 2, num_cols / 2), + ); + Garden { + rocks, + start: Position(0, 0), + extents, + } +} + +#[aoc(day21, part1)] +fn part1(map: &Garden) -> u32 { + let mut visited: HashSet = HashSet::new(); + visited.insert(map.start); + for _ in 0..STEPS { + let mut new_visited = HashSet::new(); + for pos in visited.iter().clone() { + let neighbors = [ + Position(pos.0 - 1, pos.1), + Position(pos.0 + 1, pos.1), + Position(pos.0, pos.1 - 1), + Position(pos.0, pos.1 + 1), + ] + .into_iter() + .filter(|p| in_bounds(*p, map.extents) && !map.rocks.contains(p)); + for neighbor in neighbors { + new_visited.insert(neighbor); + } + } + visited = new_visited; + } + visited.len() as u32 +} + +fn in_bounds(pos: Position, extents: (Position, Position)) -> bool { + pos.0 >= extents.0 .0 && pos.0 <= extents.1 .0 && pos.1 >= extents.0 .1 && pos.1 < extents.1 .1 +} + +fn in_bounds_part2(pos: Position, extents: Position) -> bool { + pos.0 >= 0 && pos.0 <= extents.0 && pos.1 >= 0 && pos.1 < extents.1 +} + +struct GardenPart2 { + rocks: HashSet, + start: Position, + extents: Position, +} + +#[aoc_generator(day21, part2)] +fn parse_part2(input: &str) -> GardenPart2 { + let mut rocks: HashSet = HashSet::new(); + for (row, line) in input.lines().enumerate() { + for (col, ch) in line.chars().enumerate() { + match ch { + '#' => { + rocks.insert(Position(row as i32, col as i32)); + } + _ => {} + }; + } + } + let num_rows = input.lines().count() as i32; + let num_cols = input.lines().next().unwrap().chars().count() as i32; + let mut expanded_rocks: HashSet = HashSet::new(); + for rock in rocks.iter() { + for row_mul in 0..5 { + for col_mul in 0..5 { + let expanded_rock = Position( + rock.0 + (num_rows * row_mul as i32), + rock.1 + (num_cols * col_mul as i32), + ); + expanded_rocks.insert(expanded_rock); + } + } + } + GardenPart2 { + rocks: expanded_rocks, + start: Position(num_rows * 5 / 2, num_cols * 5 / 2), + extents: Position(num_rows * 5, num_cols * 5), + } +} + +#[aoc(day21, part2)] +fn part2(map: &GardenPart2) -> i64 { + let b0: i64 = walk(&map, 65) as i64; + let b1: i64 = walk(&map, 65 + 131) as i64; + let b2: i64 = walk(&map, 65 + 2 * 131) as i64; + let n: i64 = 202300; + // below uses Cramer's Rule to solve for x0, x1, x2 + let det_a: f64 = -2.0; + let det_a0: f64 = -b0 as f64 + 2.0 * b1 as f64 - b2 as f64; + let det_a1: f64 = 3.0 * b0 as f64 - 4.0 * b1 as f64 + b2 as f64; + let det_a2: f64 = -2.0 * b0 as f64; + let x0: i64 = (det_a0 / det_a) as i64; + let x1: i64 = (det_a1 / det_a) as i64; + let x2: i64 = (det_a2 / det_a) as i64; + + x0 * n * n + x1 * n + x2 +} + +fn walk(garden: &GardenPart2, steps: u32) -> u32 { + let mut visited: HashSet = HashSet::new(); + visited.insert(garden.start); + for _ in 0..steps { + let mut new_visited = HashSet::new(); + for pos in visited.iter().clone() { + let neighbors = [ + Position(pos.0 - 1, pos.1), + Position(pos.0 + 1, pos.1), + Position(pos.0, pos.1 - 1), + Position(pos.0, pos.1 + 1), + ] + .into_iter() + .filter(|p| in_bounds_part2(*p, garden.extents) && !garden.rocks.contains(p)); + for neighbor in neighbors { + new_visited.insert(neighbor); + } + } + visited = new_visited; + } + visited.len() as u32 +} + + +#[cfg(test)] +mod tests { + use super::*; + + const EX: &str = r"........... +.....###.#. +.###.##..#. +..#.#...#.. +....#.#.... +.##..S####. +.##..#...#. +.......##.. +.##.#.####. +.##..##.##. +..........."; + + #[test] + fn part1_example() { + assert_eq!(part1(&parse(EX)), 16); + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 507f881..14025a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +mod day21; mod day20; mod day19; mod day18;