From 295bfed6b4184654c0d5caf3897351b57ebee1a5 Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Fri, 22 Dec 2023 01:59:35 +0100 Subject: [PATCH] Optimzed the solution for day 21 and actually calculate the answer for part 2 in code --- 2023/src/bin/day21.rs | 131 +++++++++++++++++++----------------------- 1 file changed, 58 insertions(+), 73 deletions(-) diff --git a/2023/src/bin/day21.rs b/2023/src/bin/day21.rs index 42d1a46..8104157 100644 --- a/2023/src/bin/day21.rs +++ b/2023/src/bin/day21.rs @@ -15,8 +15,8 @@ mod tests { #[test] fn part1_test1() -> Result<()> { - // The example provided uses 6 steps instead of 64, however this should be the correct - // answer for 64 steps with the example + // The example only provides an answer for 6 steps. (16) + // This should be the correct answer for running the example with 64 steps Day::test(Day::part1, "test-1", 42) } @@ -25,9 +25,11 @@ mod tests { Day::test(Day::part1, "input", 3642) } + // There is no test case for part 2 + #[test] - fn part2_test1() -> Result<()> { - Day::test(Day::part2, "test-1", 154) + fn part2_solution() -> Result<()> { + Day::test(Day::part2, "input", 608603023105276) } // Benchmarks @@ -48,7 +50,7 @@ mod tests { pub struct Day; impl aoc::Solver for Day { type Output1 = usize; - type Output2 = usize; + type Output2 = isize; fn day() -> u8 { 21 @@ -64,7 +66,7 @@ impl aoc::Solver for Day { .enumerate() .map(|(x, mut c)| { if c == 'S' { - queue.push(((x as isize, y as isize), 64)); + queue.push((x as isize, y as isize)); c = '.'; } @@ -74,49 +76,35 @@ impl aoc::Solver for Day { }) .collect(); - let mut visited = HashSet::new(); + let mut set = HashSet::new(); let directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]; - // Depth first - while let Some(step) = queue.pop() { - // If we have already evaluated this state, do not add it to the queue - if visited.contains(&step) { - continue; - } + for _ in 0..64 { + while let Some(step) = queue.pop() { + for direction in directions { + let next = (step.0 + direction.0, step.1 + direction.1); - // Mark this node as visited - visited.insert(step); - - // Check if we have ran out of steps - if step.1 == 0 { - continue; - } - - // Try moving in all directions - for direction in directions { - let next = ( - (step.0 .0 + direction.0, step.0 .1 + direction.1), - step.1 - 1, - ); - - // If the tile is free add it to the queue - if let Some(&tile) = map.get(&next.0) { - if tile == '.' { - queue.push(next); + // If the tile is free add it to the queue + if let Some(&tile) = map.get(&next) { + if tile == '.' { + set.insert(next); + } } } } + + queue = set.into_iter().collect(); + set = HashSet::new(); } - visited.iter().filter(|step| step.1 == 0).count() + queue.len() } fn part2(input: &str) -> Self::Output2 { - let height = input.lines().count(); - let width = input.lines().next().unwrap().chars().count(); - println!("{width}, {height}"); - // Map is square: 131 x 131 - // !!! Others have observed 26501365 = 202300 * 131 + 65 !!! - // Is there a pattern for i * 131 + 65? + // All maps are square + let size = input.lines().count(); + // Map is square: 131 x 131 => size = 131 + // !!! Others have observed 26501365 = 202300 * size + size/2 !!! + // Is there a pattern for i * size + size/2? // i = 0 => 3776 // i = 1 => 33652 // i = 2 => 93270 @@ -125,9 +113,9 @@ impl aoc::Solver for Day { // 3642 - 14737 x + 14871 x^2 // i = 202300 => 608603023105276 // Could not have done this without a hint from Reddit... - const N: usize = 2 * 131 + 65; + let i: isize = 202300; - let mut queue = VecDeque::new(); + let mut queue = Vec::new(); let map: HashMap<(isize, isize), char> = input .lines() .enumerate() @@ -136,7 +124,7 @@ impl aoc::Solver for Day { .enumerate() .map(|(x, mut c)| { if c == 'S' { - queue.push_back(((x as isize, y as isize), 0)); + queue.push((x as isize, y as isize)); c = '.'; } @@ -146,44 +134,41 @@ impl aoc::Solver for Day { }) .collect(); - let mut visited = HashSet::new(); + let mut nums = Vec::new(); + let mut set = HashSet::new(); let directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]; - // Depth first - while let Some(step) = queue.pop_front() { - // If we have already evaluated this state, do not add it to the queue - if visited.contains(&step) { - continue; - } + for n in 0..(2 * size + size / 2) { + while let Some(step) = queue.pop() { + for direction in directions { + let next = (step.0 + direction.0, step.1 + direction.1); - // Mark this node as visited - visited.insert(step); + let next_wrapped = ( + next.0.rem_euclid(size as isize), + next.1.rem_euclid(size as isize), + ); - // Check if we have ran out of steps - if step.1 == N { - continue; - } - - // Try moving in all directions - for direction in directions { - let next = ( - (step.0 .0 + direction.0, step.0 .1 + direction.1), - step.1 + 1, - ); - - let next_wrapped = ( - next.0 .0.rem_euclid(width as isize), - next.0 .1.rem_euclid(height as isize), - ); - - // If the tile is free add it to the queue - if let Some(&tile) = map.get(&next_wrapped) { - if tile == '.' { - queue.push_back(next); + // If the tile is free add it to the queue + if let Some(&tile) = map.get(&next_wrapped) { + if tile == '.' { + set.insert(next); + } } } } + + queue = set.into_iter().collect(); + set = HashSet::new(); + + if n + 1 == nums.len() * size + size / 2 { + nums.push(queue.len() as isize); + } } - visited.iter().filter(|step| step.1 == N).count() + // Using linear algebra these solutions can be found + let a = (nums[0] - 2 * nums[1] + nums[2]) / 2; + let b = (4 * nums[1] - 3 * nums[0] - nums[2]) / 2; + let c = nums[0]; + + a * i.pow(2) + b * i + c } }