This commit is contained in:
Andrew Glaze 2023-12-21 15:21:44 -05:00
parent a19a3258c2
commit 9db0e3f054
3 changed files with 187 additions and 1 deletions

View File

@ -1,5 +1,4 @@
use std::collections::{HashMap, VecDeque}; use std::collections::{HashMap, VecDeque};
use aoc_runner_derive::{aoc, aoc_generator}; use aoc_runner_derive::{aoc, aoc_generator};
use num::Integer; use num::Integer;

186
src/day21.rs Normal file
View File

@ -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<Position>,
start: Position,
extents: (Position, Position),
}
#[aoc_generator(day21, part1)]
fn parse(input: &str) -> Garden {
let mut rocks: HashSet<Position> = 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<Position> = 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<Position> = 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<Position>,
start: Position,
extents: Position,
}
#[aoc_generator(day21, part2)]
fn parse_part2(input: &str) -> GardenPart2 {
let mut rocks: HashSet<Position> = 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<Position> = 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<Position> = 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);
}
}

View File

@ -1,3 +1,4 @@
mod day21;
mod day20; mod day20;
mod day19; mod day19;
mod day18; mod day18;