From b0d1c83ef9011b6847a5dc8cdd491a02f7b9bbf1 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 17 Dec 2024 19:39:45 +0100 Subject: [PATCH] feat: add day 15 --- README.md | 3 +- data/examples/15.txt | 21 +++ src/bin/15.rs | 351 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 374 insertions(+), 1 deletion(-) create mode 100644 data/examples/15.txt create mode 100644 src/bin/15.rs diff --git a/README.md b/README.md index 357de73..0211213 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,9 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www. | [Day 12](./src/bin/12.rs) | `332.4ms` | `318.8ms` | | [Day 13](./src/bin/13.rs) | `133.3µs` | `92.0µs` | | [Day 14](./src/bin/14.rs) | `210.1µs` | `105.4ms` | +| [Day 15](./src/bin/15.rs) | `95.2ms` | `188.1ms` | -**Total: 12983.70ms** +**Total: 13267.00ms** --- diff --git a/data/examples/15.txt b/data/examples/15.txt new file mode 100644 index 0000000..84cf1fb --- /dev/null +++ b/data/examples/15.txt @@ -0,0 +1,21 @@ +########## +#..O..O.O# +#......O.# +#.OO..O.O# +#..O@..O.# +#O#..O...# +#O..O..O.# +#.OO.O.OO# +#....O...# +########## + +^v>^vv^v>v<>v^v<<><>>v^v^>^<<<><^ +vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<^<^^>>>^<>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^v^^<^^vv< +<>^^^^>>>v^<>vvv^>^^^vv^^>v<^^^^v<>^>vvvv><>>v^<<^^^^^ +^><^><>>><>^^<<^^v>>><^^>v>>>^v><>^v><<<>vvvv>^<><<>^>< +^>><>^v<><^vvv<^^<><^v<<<><<<^^<^>>^<<<^>>^v^>>^v>vv>^<<^v<>><<><<>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^ +<><^^>^^^<>^vv<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<> +^^>vv<^v^v^<>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<>< +v^^>>><<^^<>>^v^v^<<>^<^v^v><^<<<><<^vv>>v>v^<<^ diff --git a/src/bin/15.rs b/src/bin/15.rs new file mode 100644 index 0000000..80dac4c --- /dev/null +++ b/src/bin/15.rs @@ -0,0 +1,351 @@ +advent_of_code::solution!(15); + +use std::collections::vec_deque::VecDeque; +use std::collections::HashMap; + +use std::iter::successors; + +#[derive(Debug, Hash, Clone, PartialEq, Eq)] +struct Pos { + x: i16, + y: i16, +} + +impl Pos { + fn init(x: i16, y: i16) -> Self { + Self { x, y } + } + + fn mv(&self, dir: Dir) -> Self { + match dir { + Dir::Up => Pos { + x: self.x, + y: self.y - 1, + }, + Dir::Down => Pos { + x: self.x, + y: self.y + 1, + }, + Dir::Left => Pos { + x: self.x - 1, + y: self.y, + }, + Dir::Right => Pos { + x: self.x + 1, + y: self.y, + }, + } + } + + fn to_p2(&self) -> (Pos, Pos) { + ( + Pos { + x: self.x * 2, + y: self.y, + }, + Pos { + x: self.x * 2 + 1, + y: self.y, + }, + ) + } + + fn to_gps(&self) -> u32 { + (self.y as u32) * 100 + (self.x as u32) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum Dir { + Up, + Down, + Left, + Right, +} + +impl TryFrom for Dir { + type Error = (); + fn try_from(c: char) -> Result { + match c { + '<' => Ok(Dir::Left), + '>' => Ok(Dir::Right), + '^' => Ok(Dir::Up), + 'v' => Ok(Dir::Down), + _ => Err(()), + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum Tile { + Empty, + Wall, + Crate, + CrateL, + CrateR, + Bot, +} + +impl TryFrom for Tile { + type Error = (); + fn try_from(c: char) -> Result { + match c { + '.' => Ok(Tile::Empty), + '#' => Ok(Tile::Wall), + '@' => Ok(Tile::Bot), + 'O' => Ok(Tile::Crate), + _ => Err(()), + } + } +} + +impl std::fmt::Debug for Tile { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + match self { + Tile::Empty => write!(f, ".")?, + Tile::Wall => write!(f, "#")?, + Tile::Bot => write!(f, "@")?, + Tile::Crate => write!(f, "O")?, + Tile::CrateL => write!(f, "[")?, + Tile::CrateR => write!(f, "]")?, + } + Ok(()) + } +} + +#[derive(Clone)] +struct State { + map: HashMap, + bot: Pos, + moves: VecDeque, + size: Pos, + p2: bool, +} + +impl State { + fn to_p2(&self) -> Option { + let mut bot: Option = None; + let mut new_map: HashMap = HashMap::new(); + for (p, t) in self.map.iter() { + let (p1, p2) = p.to_p2(); + if *t == Tile::Bot { + bot = Some(p1.clone()); + new_map.insert(p1, Tile::Bot); + new_map.insert(p2, Tile::Empty); + } else if *t == Tile::Crate { + new_map.insert(p1, Tile::CrateL); + new_map.insert(p2, Tile::CrateR); + } else { + new_map.insert(p1, *t); + new_map.insert(p2, *t); + } + } + + bot.map(|bot| Self { + map: new_map, + bot, + moves: self.moves.clone(), + size: Pos::init(self.size.x * 2, self.size.y), + p2: true, + }) + } + + fn score(&self) -> u32 { + self.map + .iter() + .filter_map(|(k, v)| match v { + Tile::Crate => Some(k.to_gps()), + Tile::CrateL => Some(k.to_gps()), + _ => None, + }) + .sum::() + } + + // TODO Probably it could be homogenized... + fn mv(&mut self, pos: Pos, dir: Dir) -> bool { + let pos_tile = self.map.get(&pos); + let dest = pos.mv(dir); + let dest_tile = self.map.get(&dest); + let doable = match (pos_tile, dest_tile) { + (None, _) => false, + (_, None) => true, // ? + (Some(Tile::Wall), _) => false, + (Some(Tile::Empty), _) => false, + (_, Some(Tile::Empty)) => true, + (_, Some(Tile::Crate)) => self.mv(dest.clone(), dir), + (_, Some(Tile::CrateL)) => self.mv(dest.clone(), dir), + (_, Some(Tile::CrateR)) => self.mv(dest.clone(), dir), + _ => false, + }; + if doable { + let pos_tile = self.map.get(&pos).unwrap(); + self.map.insert(dest, *pos_tile); + self.map.insert(pos, Tile::Empty); + } + doable + } + + fn move_possible(&self, pos: &Pos, dir: Dir) -> bool { + let pos_tile = self.map.get(pos); + let dest = pos.mv(dir); + let dest_tile = self.map.get(&dest); + match (pos_tile, dest_tile) { + (None, _) => false, + (_, None) => false, + (Some(Tile::Wall), _) => false, + (Some(Tile::Empty), _) => false, + (_, Some(Tile::Empty)) => true, + (_, Some(Tile::Crate)) => self.move_possible(&dest, dir), + (_, Some(Tile::CrateL)) if dir == Dir::Left || dir == Dir::Right => { + self.move_possible(&dest, dir) + } + (_, Some(Tile::CrateL)) if dir == Dir::Up || dir == Dir::Down => { + self.move_possible(&dest, dir) + && self.move_possible(&dest.clone().mv(Dir::Right), dir) + } + (_, Some(Tile::CrateR)) if dir == Dir::Left || dir == Dir::Right => { + self.move_possible(&dest, dir) + } + (_, Some(Tile::CrateR)) if dir == Dir::Up || dir == Dir::Down => { + self.move_possible(&dest, dir) + && self.move_possible(&dest.clone().mv(Dir::Left), dir) + } + _ => false, + } + } + + fn do_recursive_move(&mut self, pos: Pos, dir: Dir) { + let dest = pos.mv(dir); + let dest_tile = self.map.get(&dest).unwrap(); + if dest_tile == &Tile::Crate { + self.do_recursive_move(dest.clone(), dir); + } else if dest_tile == &Tile::CrateL { + self.do_recursive_move(dest.clone(), dir); + self.do_recursive_move(dest.clone().mv(Dir::Right), dir); + } else if dest_tile == &Tile::CrateR { + self.do_recursive_move(dest.clone(), dir); + self.do_recursive_move(dest.clone().mv(Dir::Left), dir); + } + let pos_tile = self.map.get(&pos).unwrap(); + self.map.insert(dest, *pos_tile); + self.map.insert(pos, Tile::Empty); + } + + fn mv_p2(&mut self, pos: Pos, dir: Dir) -> bool { + if !self.move_possible(&pos, dir) { + return false; + } + self.do_recursive_move(pos, dir); + true + } + + fn next(&self) -> Option { + let mut new_state = self.clone(); + let move_dir = new_state.moves.pop_front()?; + let dest = new_state.bot.mv(move_dir); + if (self.p2 + && (move_dir == Dir::Up || move_dir == Dir::Down) + && new_state.mv_p2(new_state.bot.clone(), move_dir)) + || (new_state.mv(new_state.bot.clone(), move_dir)) + { + new_state.bot = dest; + } + Some(new_state) + } +} + +impl std::fmt::Debug for State { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + writeln!(f, "State:\nSize: {:?} - Bot: {:?}", self.size, self.bot)?; + for y in 0..self.size.y { + for x in 0..self.size.x { + if let Some(t) = self.map.get(&Pos::init(x, y)) { + write!(f, "{:?}", t)?; + } + } + writeln!(f)?; + } + writeln!(f, "Moves Left: {:?}", self.moves.len())?; + Ok(()) + } +} + +impl TryFrom<&str> for State { + type Error = (); + fn try_from(input: &str) -> Result { + let (_, map, moves, size): (bool, HashMap, Vec, Pos) = + input.lines().enumerate().try_fold( + (true, HashMap::new(), Vec::new(), Pos::init(0, 0)), + |acc, (y, l)| { + let (parse_map, mut map, mut moves, size) = acc; + if l.is_empty() { + Ok((false, map, moves, size)) + } else if parse_map { + let new_map: Result, ()> = l + .chars() + .enumerate() + .map(|(x, c)| { + let p = Pos::init(x as i16, y as i16); + Tile::try_from(c).map(|t| (p, t)) + }) + .collect(); + if let Ok(new_map) = new_map { + for (p, t) in new_map { + map.insert(p, t); + } + Ok((true, map, moves, Pos::init(l.len() as i16, y as i16 + 1))) + } else { + Err(()) + } + } else { + moves.extend(l.chars().flat_map(Dir::try_from)); + Ok((false, map, moves, size)) + } + }, + )?; + if let Some((bot, _)) = map.iter().find(|(_, t)| **t == Tile::Bot) { + Ok(Self { + bot: bot.clone(), + map, + moves: moves.into(), + size, + p2: false, + }) + } else { + Err(()) + } + } +} + +pub fn part_one(input: &str) -> Option { + successors(State::try_from(input).ok(), |st| st.next()) + .last() + .unwrap() + .score() + .into() +} + +pub fn part_two(input: &str) -> Option { + successors(State::try_from(input).ok()?.to_p2(), |st| st.next()) + .last() + .unwrap() + .score() + .into() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(10092)); + } + + #[test] + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(9021)); + } +}