--- /dev/null
+use std::{
+ collections::{HashMap, HashSet, VecDeque},
+ io::BufRead,
+};
+
+type Pos = (i32, i32);
+
+pub struct CorruptedLocations {
+ positions: Vec<Pos>,
+ x_max: i32,
+ y_max: i32,
+}
+
+pub fn read_byte_positions(reader: &mut dyn BufRead) -> CorruptedLocations {
+ let mut positions = Vec::new();
+ for l in reader.lines() {
+ let pos: Vec<i32> = l
+ .unwrap()
+ .split(",")
+ .map(|s| s.parse::<i32>().unwrap())
+ .collect();
+ positions.push((pos[0], pos[1]));
+ }
+
+ let x_max = positions.iter().max_by(|a, b| a.0.cmp(&b.0)).unwrap().0;
+ let y_max = positions.iter().max_by(|a, b| a.1.cmp(&b.1)).unwrap().1;
+
+ CorruptedLocations {
+ positions,
+ x_max,
+ y_max,
+ }
+}
+
+fn neighbours(x: i32, y: i32) -> [Pos; 4] {
+ [(x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)]
+}
+
+pub fn shortest_path(
+ corrupted_locations: &CorruptedLocations,
+ n_first_location: usize,
+) -> Vec<Pos> {
+ let locations: HashSet<&Pos> =
+ HashSet::from_iter(corrupted_locations.positions[0..n_first_location].iter());
+ let mut heads = VecDeque::from([(0, 0)]);
+
+ let mut visited: HashMap<Pos, Pos> = HashMap::from([((0, 0), (0, 0))]);
+
+ while let Some((x, y)) = heads.pop_front() {
+ for (xn, yn) in neighbours(x, y) {
+ if xn < 0
+ || xn > corrupted_locations.x_max
+ || yn < 0
+ || yn > corrupted_locations.y_max
+ || locations.contains(&(xn, yn))
+ {
+ continue;
+ }
+
+ if xn == corrupted_locations.x_max && yn == corrupted_locations.y_max {
+ let mut path = vec![(xn, yn)];
+ let (mut x, mut y) = (x, y);
+ while x != 0 || y != 0 {
+ path.push((x, y));
+ (x, y) = visited[&(x, y)];
+ }
+ return path;
+ }
+
+ visited.entry((xn, yn)).or_insert_with(|| {
+ heads.push_back((xn, yn));
+ (x, y)
+ });
+ }
+ }
+
+ Vec::new()
+}
+
+pub fn first_position_cutting_the_path(
+ corrupted_locations: &CorruptedLocations,
+ start_length: usize,
+) -> Pos {
+ let mut path = shortest_path(corrupted_locations, start_length);
+ for l in start_length + 1..=corrupted_locations.positions.len() {
+ if path.contains(&corrupted_locations.positions[l - 1]) {
+ path = shortest_path(corrupted_locations, l);
+ // Can't find a path -> return the previous corrupted byte position.
+ if path.is_empty() {
+ return corrupted_locations.positions[l - 1];
+ }
+ }
+ }
+
+ (0, 0)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ static BYTE_POSITIONS: &str = "5,4
+4,2
+4,5
+3,0
+2,1
+6,3
+2,4
+1,5
+0,6
+3,3
+2,6
+5,1
+1,2
+5,5
+2,5
+6,5
+1,4
+0,4
+6,4
+1,1
+6,1
+1,0
+0,5
+1,6
+2,0";
+
+ #[test]
+ fn part1() {
+ let positions = read_byte_positions(&mut BYTE_POSITIONS.as_bytes());
+ assert_eq!(shortest_path(&positions, 12).len(), 22);
+ }
+
+ #[test]
+ fn part2() {
+ let positions = read_byte_positions(&mut BYTE_POSITIONS.as_bytes());
+ let pos = first_position_cutting_the_path(&positions, 12);
+ assert_eq!(pos, (6, 1));
+ }
+}
let reg_a = day17::fix_corrupted_reg_a(&mut state, &program);
format!("part1: '{output}', part2: {reg_a}")
}
+
+pub fn day18(reader: &mut dyn BufRead) -> String {
+ let corrupted_locations = day18::read_byte_positions(reader);
+ let path = day18::shortest_path(&corrupted_locations, 1024);
+ let (x, y) = day18::first_position_cutting_the_path(&corrupted_locations, 1024);
+ format!("part1: {}, part2: {},{}", path.len(), x, y)
+}