retroactive, to derust my rust
1use std::collections::HashSet;
2
3pub fn day6_part1(input: &str) -> String {
4 let map = Map::parse(input);
5 map.step_forever().len().to_string()
6}
7
8pub fn day6_part2(input: &str) -> String {
9 let map = Map::parse(input);
10 map.new_maps()
11 .map(|map| map.loops())
12 .filter(|&map| map)
13 .count()
14 .to_string()
15}
16
17#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)]
18struct Point {
19 row: usize,
20 col: usize,
21}
22
23impl Point {
24 fn step(self, dir: CardinalDirection) -> Self {
25 match dir {
26 CardinalDirection::Up => self.up(),
27 CardinalDirection::Left => self.left(),
28 CardinalDirection::Right => self.right(),
29 CardinalDirection::Down => self.down(),
30 }
31 }
32 fn up(self) -> Self {
33 Point {
34 row: self.row - 1,
35 ..self
36 }
37 }
38 fn down(self) -> Self {
39 Point {
40 row: self.row + 1,
41 ..self
42 }
43 }
44 fn left(self) -> Self {
45 Point {
46 col: self.col - 1,
47 ..self
48 }
49 }
50 fn right(self) -> Self {
51 Point {
52 col: self.col + 1,
53 ..self
54 }
55 }
56}
57
58#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
59enum CardinalDirection {
60 Up,
61 Left,
62 Right,
63 Down,
64}
65
66impl CardinalDirection {
67 fn rotate_90degrees_clockwise(self) -> Self {
68 match self {
69 Self::Up => Self::Right,
70 Self::Left => Self::Up,
71 Self::Right => Self::Down,
72 Self::Down => Self::Left,
73 }
74 }
75}
76
77impl From<char> for CardinalDirection {
78 fn from(c: char) -> Self {
79 match c {
80 '^' => Self::Up,
81 'v' | 'V' => Self::Down,
82 '<' => Self::Left,
83 '>' => Self::Right,
84 _ => unreachable!(),
85 }
86 }
87}
88
89#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
90struct Guard {
91 location: Point,
92 direction: CardinalDirection,
93}
94
95impl Guard {
96 fn step(self) -> Self {
97 Self {
98 location: self.location.step(self.direction),
99 ..self
100 }
101 }
102 fn step_obstacle(self) -> Self {
103 let new_dir = self.direction.rotate_90degrees_clockwise();
104 Self {
105 direction: new_dir,
106 ..self
107 }
108 }
109}
110
111struct Map {
112 width: usize,
113 height: usize,
114 guard: Guard,
115 obstructions: HashSet<Point>,
116 visited: HashSet<Guard>,
117 stuck_in_a_loop: bool,
118}
119
120impl Map {
121 fn new_maps(self) -> impl Iterator<Item = Self> {
122 (0..self.width)
123 .flat_map(|col| (0..self.height).map(move |row| Point { row, col }))
124 .filter(|point| &self.guard.location != point && !self.obstructions.contains(point))
125 .collect::<Vec<_>>()
126 .into_iter()
127 .map(move |point| Self {
128 obstructions: {
129 let mut obstructions = self.obstructions.clone();
130 obstructions.insert(point);
131 obstructions
132 },
133 visited: self.visited.clone(),
134 ..self
135 })
136 }
137
138 fn loops(mut self) -> bool {
139 while self.step() && !self.stuck_in_a_loop {}
140 self.stuck_in_a_loop
141 }
142
143 fn step_forever(mut self) -> HashSet<Point> {
144 while self.step() {}
145 self.visited.iter().map(|g| g.location).collect()
146 }
147
148 /// returns whether a step was taken
149 fn step(&mut self) -> bool {
150 self.stuck_in_a_loop |= !self.visited.insert(self.guard);
151 if self.is_guard_about_to_leave() {
152 return false;
153 }
154
155 if self.is_guard_facing_obstruction() {
156 self.guard = self.guard.step_obstacle();
157 } else {
158 self.guard = self.guard.step()
159 }
160
161 true
162 }
163
164 fn is_guard_facing_obstruction(&self) -> bool {
165 if self.is_guard_about_to_leave() {
166 return false;
167 }
168 let point = self.guard.step().location;
169 self.obstructions.contains(&point)
170 }
171
172 fn is_guard_about_to_leave(&self) -> bool {
173 match self.guard.direction {
174 CardinalDirection::Up => self.guard.location.row == 0,
175 CardinalDirection::Down => self.guard.location.row == self.height - 1,
176 CardinalDirection::Left => self.guard.location.col == 0,
177 CardinalDirection::Right => self.guard.location.col == self.width - 1,
178 }
179 }
180
181 fn parse(input: &str) -> Self {
182 let lines = input.lines().collect::<Vec<_>>();
183 let height = lines.len();
184 let width = lines[0].chars().count();
185 let guard = lines
186 .iter()
187 .enumerate()
188 .find_map(|(row, line)| {
189 line.char_indices()
190 .find(|&(_, ch)| ch == '^' || ch == '>' || ch == '<' || ch == 'V' || ch == 'v')
191 .map(|(col, _)| (row, col))
192 })
193 .unwrap();
194 let obstructions = lines
195 .iter()
196 .enumerate()
197 .flat_map(|(row, line)| {
198 line.char_indices()
199 .filter(|&(_, ch)| ch == '#')
200 .map(move |(col, _): (usize, char)| Point { row, col })
201 })
202 .collect();
203
204 Self {
205 width,
206 height,
207 guard: Guard {
208 location: Point {
209 row: guard.0,
210 col: guard.1,
211 },
212 direction: lines[guard.0].chars().nth(guard.1).unwrap().into(),
213 },
214 obstructions,
215 visited: HashSet::new(),
216 stuck_in_a_loop: false,
217 }
218 }
219}