From 8421d51e0eedc535121256a641385edacff75933 Mon Sep 17 00:00:00 2001 From: Germain Chazot Date: Sun, 15 Dec 2024 22:44:50 +0100 Subject: [PATCH] 2024 Day 15 part 2 --- year_2024/src/day15.rs | 255 ++++++++++++++++++++++++++++++----------- 1 file changed, 187 insertions(+), 68 deletions(-) diff --git a/year_2024/src/day15.rs b/year_2024/src/day15.rs index 239ac7d..37266f9 100644 --- a/year_2024/src/day15.rs +++ b/year_2024/src/day15.rs @@ -1,10 +1,14 @@ +use std::collections::{HashSet, VecDeque}; + pub fn execute() -> String { let data = aoc_utils::read_lines("input/day15.txt"); - let mut warehouse = Warehouse::from_lines(data); + let mut warehouse = Warehouse::from_lines(data.clone(), false); while warehouse.progress() {} - let part1 = warehouse.checksum(); - let part2 = 456; + + let mut warehouse2 = Warehouse::from_lines(data, true); + while warehouse2.progress() {} + let part2 = warehouse2.checksum(); format!("{} {}", part1, part2) } @@ -14,18 +18,21 @@ enum Tile { Wall, Floor, Box, + BoxLeft, + BoxRight, } struct Warehouse { - size: usize, + size: (usize, usize), tiles: Vec>, robot: (usize, usize), instructions: Vec<(isize, isize)>, current: usize, + is_part2: bool, } impl Warehouse { - fn from_lines(lines: Vec) -> Warehouse { - let size = lines[0].len(); + fn from_lines(lines: Vec, is_part2: bool) -> Warehouse { + let input_width = lines[0].len(); let mut tiles = vec![]; let mut robot = (0, 0); @@ -37,29 +44,43 @@ impl Warehouse { break; } - assert_eq!(line.len(), size); - - let row = line - .chars() - .enumerate() - .map(|(i, c)| match c { - '#' => Tile::Wall, - 'O' => Tile::Box, - '.' => Tile::Floor, - '@' => { - robot.0 = i; - robot.1 = j; - Tile::Floor - } - _ => unreachable!("Should not be here but {:?}", (j, i)), - }) - .collect::>(); + assert_eq!(line.len(), input_width); + + let row_iter = line.chars().enumerate().map(|(i, c)| match c { + '#' => Tile::Wall, + '.' => Tile::Floor, + 'O' => Tile::Box, + '@' => { + robot.0 = if is_part2 { i * 2 } else { i }; + robot.1 = j; + Tile::Floor + } + _ => unreachable!("Should not be here but {:?}", (j, i)), + }); + + let row = if is_part2 { + row_iter + .flat_map(|tile| match tile { + Tile::Box => vec![Tile::BoxLeft, Tile::BoxRight], + other => vec![other.clone(), other], + }) + .collect::>() + } else { + row_iter.collect::>() + }; tiles.push(row); j += 1; } + let width = if is_part2 { + input_width * 2 + } else { + input_width + }; + let size = (width, tiles.len()); + let mut instructions = vec![]; while j < lines.len() { let line = &lines[j]; @@ -82,49 +103,125 @@ impl Warehouse { robot, instructions, current: 0, + is_part2, } } - fn next_floor(&self, from: (usize, usize), direction: (isize, isize)) -> isize { - let mut current = from; - let mut result = 0; - while current.0 > 0 && current.1 > 0 && current.0 < self.size && current.1 < self.size { - result += 1; - current.0 = (current.0 as isize + direction.0) as usize; - current.1 = (current.1 as isize + direction.1) as usize; - - match self.tiles[current.1][current.0] { - Tile::Box => { - continue; + fn push( + &self, + from: (usize, usize), + direction: (isize, isize), + ) -> Option> { + let mut result = HashSet::new(); + + if !self.is_part2 { + let mut next_floor_tile = 0; + + let mut current = from; + while current.0 > 0 + && current.1 > 0 + && current.0 < self.size.0 + && current.1 < self.size.1 + { + next_floor_tile += 1; + current.0 = (current.0 as isize + direction.0) as usize; + current.1 = (current.1 as isize + direction.1) as usize; + + match self.tiles[current.1][current.0] { + Tile::Box => { + continue; + } + Tile::Floor => { + break; + } + Tile::Wall => { + return None; + } + Tile::BoxLeft | Tile::BoxRight => { + panic!("This code does not apply to wide boxes"); + } } - Tile::Floor => { - return result; + } + + if next_floor_tile > 1 { + for i in 1..next_floor_tile { + result.insert(( + (from.0 as isize + i * direction.0) as usize, + (from.1 as isize + i * direction.1) as usize, + )); } - Tile::Wall => { - return 0; + } + } else { + let mut to_check = VecDeque::from([from]); + while let Some(check) = to_check.pop_front() { + let next = ( + (check.0 as isize + direction.0) as usize, + (check.1 as isize + direction.1) as usize, + ); + let is_horizontal = direction.1 == 0; + match self.tiles[next.1][next.0] { + Tile::Box => { + panic!("This code does not apply to part 1"); + } + Tile::Floor => { + continue; + } + Tile::Wall => { + return None; + } + Tile::BoxLeft | Tile::BoxRight if is_horizontal => { + result.insert(next); + to_check.push_back(next) + } + Tile::BoxRight if !is_horizontal => { + // RHS of a box => deal with it when dealing with LHS + let check_left = (check.0 - 1, check.1); + if !to_check.contains(&check_left) { + to_check.push_front(check_left); + } + } + Tile::BoxLeft if !is_horizontal => { + let next_right = (next.0 + 1, next.1); + result.insert(next); + result.insert(next_right); + to_check.push_back(next); + to_check.push_back(next_right); + } + _ => unreachable!("I think all cases are actually covered"), } } } - return 0; + Some(result) } fn step(&mut self, from: (usize, usize), direction: (isize, isize)) -> (usize, usize) { - let distance = self.next_floor(from, direction); - if distance == 0 { - return from; - } + let mut new_position = from; - let result = ( - (from.0 as isize + direction.0) as usize, - (from.1 as isize + direction.1) as usize, - ); - if distance > 1 { - self.tiles[(from.1 as isize + distance * direction.1) as usize] - [(from.0 as isize + distance * direction.0) as usize] = Tile::Box; - self.tiles[result.1][result.0] = Tile::Floor; + let to_move = self.push(from, direction); + + if let Some(to_move) = to_move { + let previous = to_move + .iter() + .map(|(i, j)| { + let prev_tile = self.tiles[*j][*i].clone(); + self.tiles[*j][*i] = Tile::Floor; + (*i, *j, prev_tile) + }) + .collect::>(); + + for (i, j, tile) in previous.into_iter() { + self.tiles[(j as isize + direction.1) as usize] + [(i as isize + direction.0) as usize] = tile; + } + + new_position = ( + (from.0 as isize + direction.0) as usize, + (from.1 as isize + direction.1) as usize, + ); } - result + + new_position } fn progress(&mut self) -> bool { @@ -144,7 +241,9 @@ impl Warehouse { .flat_map(|(j, row)| { row.iter() .enumerate() - .filter_map(|(i, tile)| matches!(tile, Tile::Box).then_some(i + 100 * j)) + .filter_map(|(i, tile)| { + matches!(tile, Tile::Box | Tile::BoxLeft).then_some(i + 100 * j) + }) .collect::>() }) .sum() @@ -155,7 +254,6 @@ impl Warehouse { mod tests { use super::*; use std::fmt::Display; - use std::ops::IndexMut; #[test] fn test_mine() { @@ -164,28 +262,42 @@ mod tests { #[test] fn test_from_lines() { - let warehouse = Warehouse::from_lines(_example()); - assert_eq!(warehouse.size, 10); + let warehouse = Warehouse::from_lines(_example(), false); + assert_eq!(warehouse.size, (10, 10)); assert_eq!(warehouse.instructions.len(), 700); assert_eq!(warehouse.robot, (4, 4)); + + let warehouse2 = Warehouse::from_lines(_example(), true); + assert_eq!(warehouse2.size, (20, 10)); + assert_eq!(warehouse2.instructions.len(), 700); + assert_eq!(warehouse2.robot, (8, 4)); } #[test] - fn test_next_floor() { - let warehouse = Warehouse::from_lines(_example()); - assert_eq!(warehouse.next_floor((1, 1), (1, 0)), 1); - assert_eq!(warehouse.next_floor((1, 1), (0, 1)), 1); - assert_eq!(warehouse.next_floor((1, 1), (-1, 0)), 0); - assert_eq!(warehouse.next_floor((1, 1), (0, -1)), 0); - assert_eq!(warehouse.next_floor((3, 2), (0, -1)), 0); - assert_eq!(warehouse.next_floor((3, 2), (0, 1)), 3); - assert_eq!(warehouse.next_floor((4, 7), (-1, 0)), 3); - assert_eq!(warehouse.next_floor((4, 7), (1, 0)), 2); + fn test_push() { + let warehouse = Warehouse::from_lines(_example(), false); + assert_eq!(warehouse.push((1, 1), (1, 0)), Some(HashSet::from([]))); + assert_eq!(warehouse.push((1, 1), (0, 1)), Some(HashSet::from([]))); + assert_eq!(warehouse.push((1, 1), (-1, 0)), None); + assert_eq!(warehouse.push((1, 1), (0, -1)), None); + assert_eq!(warehouse.push((3, 2), (0, -1)), None); + assert_eq!( + warehouse.push((3, 2), (0, 1)), + Some(HashSet::from([(3, 3), (3, 4)])) + ); + assert_eq!( + warehouse.push((4, 7), (-1, 0)), + Some(HashSet::from([(3, 7), (2, 7)])) + ); + assert_eq!( + warehouse.push((4, 7), (1, 0)), + Some(HashSet::from([(5, 7)])) + ); } #[test] fn test_step() { - let mut warehouse = Warehouse::from_lines(_example()); + let mut warehouse = Warehouse::from_lines(_example(), false); let state_0 = warehouse.tiles.clone(); assert_eq!(warehouse.step((1, 1), (1, 0)), (2, 1)); @@ -227,9 +339,13 @@ mod tests { #[test] fn test_progress() { - let mut warehouse = Warehouse::from_lines(_example()); + let mut warehouse = Warehouse::from_lines(_example(), false); while warehouse.progress() {} assert_eq!(warehouse.checksum(), 10092); + + let mut warehouse2 = Warehouse::from_lines(_example(), true); + while warehouse2.progress() {} + assert_eq!(warehouse2.checksum(), 9021); } impl Tile { @@ -238,6 +354,8 @@ mod tests { Tile::Floor => '.', Tile::Box => 'O', Tile::Wall => '#', + Tile::BoxLeft => '[', + Tile::BoxRight => ']', } } } @@ -260,6 +378,7 @@ mod tests { ) } } + fn _example() -> Vec { aoc_utils::read_lines("input/day15-example.txt") }