day21
This commit is contained in:
parent
a19a3258c2
commit
9db0e3f054
@ -1,5 +1,4 @@
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
use aoc_runner_derive::{aoc, aoc_generator};
|
||||
use num::Integer;
|
||||
|
||||
|
186
src/day21.rs
Normal file
186
src/day21.rs
Normal 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);
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
mod day21;
|
||||
mod day20;
|
||||
mod day19;
|
||||
mod day18;
|
||||
|
Loading…
Reference in New Issue
Block a user