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