1use crate::util::grid::*;
18use crate::util::hash::*;
19use crate::util::point::*;
20use crate::util::thread::*;
21use std::sync::atomic::{AtomicUsize, Ordering};
22
23pub fn parse(input: &str) -> Grid<u8> {
24 Grid::parse(input)
25}
26
27pub fn part1(grid: &Grid<u8>) -> usize {
29 let mut grid = grid.clone();
30 let mut position = grid.find(b'^').unwrap();
31 let mut direction = UP;
32 let mut result = 1;
33
34 while grid.contains(position + direction) {
35 if grid[position + direction] == b'#' {
36 direction = direction.clockwise();
37 continue;
38 }
39
40 let next = position + direction;
41
42 if grid[next] == b'.' {
44 result += 1;
45 grid[next] = b'^';
46 }
47
48 position = next;
49 }
50
51 result
52}
53
54pub fn part2(grid: &Grid<u8>) -> usize {
56 let mut grid = grid.clone();
57 let mut position = grid.find(b'^').unwrap();
58 let mut direction = UP;
59 let mut path = Vec::with_capacity(5_000);
60
61 while grid.contains(position + direction) {
62 if grid[position + direction] == b'#' {
63 direction = direction.clockwise();
64 }
65
66 let next = position + direction;
67
68 if grid[next] == b'.' {
70 path.push((position, direction));
71 grid[next] = b'^';
72 }
73
74 position = next;
75 }
76
77 let shortcut = Shortcut::from(&grid);
79 let total = AtomicUsize::new(0);
80
81 spawn_parallel_iterator(&path, |iter| worker(&shortcut, &total, iter));
82 total.into_inner()
83}
84
85fn worker(shortcut: &Shortcut, total: &AtomicUsize, iter: ParIter<'_, (Point, Point)>) {
86 let mut seen = FastSet::new();
87 let result = iter
88 .filter(|(position, direction)| {
89 seen.clear();
90 is_cycle(shortcut, &mut seen, *position, *direction)
91 })
92 .count();
93
94 total.fetch_add(result, Ordering::Relaxed);
95}
96
97fn is_cycle(
98 shortcut: &Shortcut,
99 seen: &mut FastSet<(Point, Point)>,
100 mut position: Point,
101 mut direction: Point,
102) -> bool {
103 let obstacle = position + direction;
104
105 while shortcut.up.contains(position) {
106 if !seen.insert((position, direction)) {
108 return true;
109 }
110
111 position = match direction {
113 UP => {
114 let next = shortcut.up[position];
115 if position.x == obstacle.x && position.y > obstacle.y && obstacle.y >= next.y {
116 obstacle - UP
117 } else {
118 next
119 }
120 }
121 DOWN => {
122 let next = shortcut.down[position];
123 if position.x == obstacle.x && position.y < obstacle.y && obstacle.y <= next.y {
124 obstacle - DOWN
125 } else {
126 next
127 }
128 }
129 LEFT => {
130 let next = shortcut.left[position];
131 if position.y == obstacle.y && position.x > obstacle.x && obstacle.x >= next.x {
132 obstacle - LEFT
133 } else {
134 next
135 }
136 }
137 RIGHT => {
138 let next = shortcut.right[position];
139 if position.y == obstacle.y && position.x < obstacle.x && obstacle.x <= next.x {
140 obstacle - RIGHT
141 } else {
142 next
143 }
144 }
145 _ => unreachable!(),
146 };
147
148 direction = direction.clockwise();
149 }
150
151 false
152}
153
154struct Shortcut {
155 up: Grid<Point>,
156 down: Grid<Point>,
157 left: Grid<Point>,
158 right: Grid<Point>,
159}
160
161impl Shortcut {
162 fn from(grid: &Grid<u8>) -> Self {
163 let mut up = grid.same_size_with(ORIGIN);
164 let mut down = grid.same_size_with(ORIGIN);
165 let mut left = grid.same_size_with(ORIGIN);
166 let mut right = grid.same_size_with(ORIGIN);
167
168 for x in 0..grid.width {
169 let mut last = Point::new(x, -1);
170
171 for y in 0..grid.height {
172 let point = Point::new(x, y);
173 if grid[point] == b'#' {
174 last = Point::new(x, y + 1);
175 }
176 up[point] = last;
177 }
178 }
179
180 for x in 0..grid.width {
181 let mut last = Point::new(x, grid.height);
182
183 for y in (0..grid.height).rev() {
184 let point = Point::new(x, y);
185 if grid[point] == b'#' {
186 last = Point::new(x, y - 1);
187 }
188 down[point] = last;
189 }
190 }
191
192 for y in 0..grid.height {
193 let mut last = Point::new(-1, y);
194
195 for x in 0..grid.width {
196 let point = Point::new(x, y);
197 if grid[point] == b'#' {
198 last = Point::new(x + 1, y);
199 }
200 left[point] = last;
201 }
202 }
203
204 for y in 0..grid.height {
205 let mut last = Point::new(grid.width, y);
206
207 for x in (0..grid.width).rev() {
208 let point = Point::new(x, y);
209 if grid[point] == b'#' {
210 last = Point::new(x - 1, y);
211 }
212 right[point] = last;
213 }
214 }
215
216 Shortcut { up, down, left, right }
217 }
218}