|
1 | 1 | use std::collections::VecDeque; |
| 2 | +use std::ops::ControlFlow; |
2 | 3 | use utils::point::Point2D; |
3 | 4 | use utils::prelude::*; |
4 | 5 |
|
5 | | -/// Finding when a path through a grid is blocked. |
| 6 | +/// Finding when the path through a grid is blocked. |
6 | 7 | #[derive(Clone, Debug)] |
7 | 8 | pub struct Day18 { |
8 | | - positions: Vec<Point2D<usize>>, |
| 9 | + blocked_at: [[u16; WIDTH]; WIDTH], |
| 10 | + fallen: u16, |
9 | 11 | } |
10 | 12 |
|
| 13 | +const MAX_COORD: usize = 70; |
| 14 | +const WIDTH: usize = MAX_COORD + 1; |
| 15 | + |
11 | 16 | impl Day18 { |
12 | 17 | pub fn new(input: &str, _: InputType) -> Result<Self, InputError> { |
13 | | - Ok(Self { |
14 | | - positions: parser::number_range(0u32..=70) |
15 | | - .repeat_n(",") |
16 | | - .map(|[x, y]| Point2D { |
17 | | - x: x as usize, |
18 | | - y: y as usize, |
19 | | - }) |
20 | | - .parse_lines(input)?, |
21 | | - }) |
| 18 | + let mut blocked_at = [[u16::MAX; WIDTH]; WIDTH]; |
| 19 | + let mut fallen = 0; |
| 20 | + for item in parser::number_range(0..=MAX_COORD) |
| 21 | + .then(parser::number_range(0..=MAX_COORD).with_prefix(b',')) |
| 22 | + .map(|(x, y)| Point2D { x, y }) |
| 23 | + .with_suffix(parser::eol()) |
| 24 | + .parse_iterator(input) |
| 25 | + { |
| 26 | + let pos = item?; |
| 27 | + if blocked_at[pos.x][pos.y] == u16::MAX { |
| 28 | + blocked_at[pos.x][pos.y] = fallen; |
| 29 | + fallen += 1; |
| 30 | + } else { |
| 31 | + return Err(InputError::new(input, 0, "duplicate position in input")); |
| 32 | + } |
| 33 | + } |
| 34 | + |
| 35 | + Ok(Self { blocked_at, fallen }) |
22 | 36 | } |
23 | 37 |
|
24 | 38 | #[must_use] |
25 | 39 | pub fn part1(&self) -> u32 { |
26 | 40 | self.minimum_steps(1024).expect("no solution found") |
27 | 41 | } |
28 | 42 |
|
29 | | - #[must_use] |
30 | | - pub fn part2(&self) -> String { |
31 | | - let mut left = 0; |
32 | | - let mut right = self.positions.len(); |
33 | | - |
34 | | - while left < right { |
35 | | - let mid = (left + right) / 2; |
36 | | - match self.minimum_steps(mid + 1) { |
37 | | - Some(_) => left = mid + 1, |
38 | | - None => right = mid, |
39 | | - } |
40 | | - } |
41 | | - |
42 | | - if left >= self.positions.len() { |
43 | | - panic!("no solution found"); |
44 | | - } |
45 | | - |
46 | | - format!("{},{}", self.positions[left].x, self.positions[left].y) |
47 | | - } |
48 | | - |
49 | | - fn minimum_steps(&self, fallen: usize) -> Option<u32> { |
50 | | - let mut blocked = [[false; 71]; 71]; |
51 | | - for pos in self.positions.iter().take(fallen) { |
52 | | - blocked[pos.x][pos.y] = true; |
53 | | - } |
54 | | - |
| 43 | + fn minimum_steps(&self, fallen: u16) -> Option<u32> { |
| 44 | + let mut blocked_at = self.blocked_at; |
55 | 45 | let mut queue = VecDeque::new(); |
56 | 46 | queue.push_back((Point2D::new(0, 0), 0)); |
57 | 47 |
|
58 | 48 | while let Some((pos, steps)) = queue.pop_front() { |
59 | | - if pos == Point2D::new(70, 70) { |
| 49 | + if pos == Point2D::new(MAX_COORD, MAX_COORD) { |
60 | 50 | return Some(steps); |
61 | 51 | } |
62 | | - if pos.x > 0 && !blocked[pos.x - 1][pos.y] { |
| 52 | + |
| 53 | + if pos.x > 0 && blocked_at[pos.x - 1][pos.y] >= fallen { |
63 | 54 | queue.push_back((Point2D::new(pos.x - 1, pos.y), steps + 1)); |
64 | | - blocked[pos.x - 1][pos.y] = true; |
| 55 | + blocked_at[pos.x - 1][pos.y] = 0; |
65 | 56 | } |
66 | | - if pos.x < 70 && !blocked[pos.x + 1][pos.y] { |
| 57 | + if pos.x < MAX_COORD && blocked_at[pos.x + 1][pos.y] >= fallen { |
67 | 58 | queue.push_back((Point2D::new(pos.x + 1, pos.y), steps + 1)); |
68 | | - blocked[pos.x + 1][pos.y] = true; |
| 59 | + blocked_at[pos.x + 1][pos.y] = 0; |
69 | 60 | } |
70 | | - if pos.y > 0 && !blocked[pos.x][pos.y - 1] { |
| 61 | + if pos.y > 0 && blocked_at[pos.x][pos.y - 1] >= fallen { |
71 | 62 | queue.push_back((Point2D::new(pos.x, pos.y - 1), steps + 1)); |
72 | | - blocked[pos.x][pos.y - 1] = true; |
| 63 | + blocked_at[pos.x][pos.y - 1] = 0; |
73 | 64 | } |
74 | | - if pos.y < 70 && !blocked[pos.x][pos.y + 1] { |
| 65 | + if pos.y < MAX_COORD && blocked_at[pos.x][pos.y + 1] >= fallen { |
75 | 66 | queue.push_back((Point2D::new(pos.x, pos.y + 1), steps + 1)); |
76 | | - blocked[pos.x][pos.y + 1] = true; |
| 67 | + blocked_at[pos.x][pos.y + 1] = 0; |
77 | 68 | } |
78 | 69 | } |
79 | 70 |
|
80 | 71 | None |
81 | 72 | } |
| 73 | + |
| 74 | + #[must_use] |
| 75 | + pub fn part2(&self) -> String { |
| 76 | + let mut blocked_at = self.blocked_at; |
| 77 | + let mut reachable = vec![None; self.fallen as usize]; |
| 78 | + let mut fallen = self.fallen; |
| 79 | + let mut next = Point2D::new(0, 0); |
| 80 | + loop { |
| 81 | + // Recursively flood fill the reachable grid spaces, tracking which grid spaces would |
| 82 | + // be reachable at a lower number of fallen bytes. |
| 83 | + if Self::fill(next, fallen, &mut blocked_at, &mut reachable).is_break() { |
| 84 | + if next == Point2D::new(0, 0) { |
| 85 | + panic!("path is never blocked"); |
| 86 | + } |
| 87 | + return format!("{},{}", next.x, next.y); |
| 88 | + } |
| 89 | + |
| 90 | + // No path to the end. Decrease fallen to the value blocking the highest reachable grid |
| 91 | + // space and try again. |
| 92 | + fallen = reachable[..fallen as usize] |
| 93 | + .iter() |
| 94 | + .rposition(Option::is_some) |
| 95 | + .expect("no solution found") as u16; |
| 96 | + next = reachable[fallen as usize].unwrap(); |
| 97 | + } |
| 98 | + } |
| 99 | + |
| 100 | + fn fill( |
| 101 | + pos: Point2D<usize>, |
| 102 | + fallen: u16, |
| 103 | + blocked_at: &mut [[u16; 71]; 71], |
| 104 | + reachable: &mut [Option<Point2D<usize>>], |
| 105 | + ) -> ControlFlow<()> { |
| 106 | + if pos == Point2D::new(MAX_COORD, MAX_COORD) { |
| 107 | + return ControlFlow::Break(()); |
| 108 | + } |
| 109 | + |
| 110 | + for dir in [Point2D::UP, Point2D::RIGHT, Point2D::DOWN, Point2D::LEFT] { |
| 111 | + let next = pos.wrapping_add_signed(dir); |
| 112 | + if next.x > MAX_COORD || next.y > MAX_COORD { |
| 113 | + continue; |
| 114 | + } |
| 115 | + |
| 116 | + if blocked_at[next.x][next.y] >= fallen { |
| 117 | + blocked_at[next.x][next.y] = 0; |
| 118 | + Self::fill(next, fallen, blocked_at, reachable)?; |
| 119 | + } else if blocked_at[next.x][next.y] > 0 { |
| 120 | + reachable[blocked_at[next.x][next.y] as usize] = Some(next); |
| 121 | + } |
| 122 | + } |
| 123 | + |
| 124 | + ControlFlow::Continue(()) |
| 125 | + } |
82 | 126 | } |
83 | 127 |
|
84 | 128 | examples!(Day18 -> (u32, &'static str) []); |
0 commit comments