From 3bc073f9b89976297a2791896d9d8fd2d92ac7c7 Mon Sep 17 00:00:00 2001 From: Keenan Tims Date: Mon, 18 Dec 2023 01:54:31 -0800 Subject: [PATCH] day18: problem 2 solution --- 18/src/main.rs | 207 ++++++++++++++++++++++++++++++------------------- 1 file changed, 126 insertions(+), 81 deletions(-) diff --git a/18/src/main.rs b/18/src/main.rs index eb6a54d..9d120dc 100644 --- a/18/src/main.rs +++ b/18/src/main.rs @@ -37,6 +37,14 @@ enum Direction { Down, } +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] +enum Turn { + LeftNinety, + RightNinety, + OneEighty, + None, +} + impl Direction { const fn all() -> &'static [Self; 4] { &[ @@ -62,6 +70,25 @@ impl Direction { Direction::Down => (0, 1), } } + fn turn_kind(&self, next_dir: Direction) -> Turn { + if *self == next_dir { + Turn::None + } else if self.opposite() == next_dir { + Turn::OneEighty + } else { + match self { + Direction::Left if next_dir == Direction::Up => Turn::RightNinety, + Direction::Left if next_dir == Direction::Down => Turn::LeftNinety, + Direction::Right if next_dir == Direction::Up => Turn::LeftNinety, + Direction::Right if next_dir == Direction::Down => Turn::RightNinety, + Direction::Up if next_dir == Direction::Left => Turn::LeftNinety, + Direction::Up if next_dir == Direction::Right => Turn::RightNinety, + Direction::Down if next_dir == Direction::Right => Turn::LeftNinety, + Direction::Down if next_dir == Direction::Left => Turn::RightNinety, + _ => unreachable!(), + } + } + } } impl From<&str> for Direction { @@ -100,7 +127,13 @@ impl From<&str> for DigInstruction { let (dir, count, color) = ( parts.next().unwrap(), parts.next().unwrap(), - parts.next().unwrap(), + parts + .next() + .unwrap() + .chars() + .skip(2) + .take(6) + .collect::(), ); Self { dir: dir.into(), @@ -110,24 +143,42 @@ impl From<&str> for DigInstruction { } } +impl DigInstruction { + fn part2_transform(&mut self) { + let (distance_s, direction_s) = self.color.split_at(5); + self.count = usize::from_str_radix(distance_s, 16).unwrap(); + self.dir = match direction_s { + "0" => Direction::Right, + "1" => Direction::Down, + "2" => Direction::Left, + "3" => Direction::Up, + s => panic!("`{}` is not a valid direction code", s) + }; + } +} + #[derive(Debug)] struct DigPlan { instructions: Vec, } +impl DigPlan { + fn part2_transform(&mut self) { + for i in &mut self.instructions { + i.part2_transform(); + } + } +} + #[derive(Debug, Clone)] struct DigTile { position: Position, - depth: usize, - color: String, } impl Default for DigTile { fn default() -> Self { Self { position: (0, 0), - depth: 0, - color: "000000".into(), } } } @@ -140,6 +191,12 @@ struct DigHole { tiles_map: HashMap, x_range: Range, y_range: Range, + area: u64, +} + +// determinant of positions p1 and p2 +fn det(p1: Position, p2: Position) -> i64 { + ((p1.0 * p2.1) - (p1.1 * p2.0)) as i64 } impl DigHole { @@ -149,88 +206,63 @@ impl DigHole { tiles_map: HashMap::new(), x_range: 0..0, y_range: 0..0, + area: 0, } } - fn pos_offset(&self, pos: Position, offset: (isize, isize)) -> Position { - (pos.0 + offset.0, pos.1 + offset.1) + fn pos_offset_n(&self, pos: Position, offset: (isize, isize), n: usize) -> Position { + (pos.0 + offset.0 * n as isize, pos.1 + offset.1 * n as isize) } fn run_plan(&mut self, plan: &DigPlan) { - // start position is 1m deep let mut cur_pos = (0, 0); - let (mut max_x, mut max_y) = (0, 0); self.tiles_loop.push_back(DigTile { position: cur_pos, - depth: 1, - color: "000000".into(), }); - for i in &plan.instructions { - println!("instruction: {:?}", i); - for _ in 0..i.count { - println!(" cur_pos: {:?}", cur_pos); - cur_pos = (cur_pos.0 + i.dir.offset().0, cur_pos.1 + i.dir.offset().1); - (max_x, max_y) = ( - std::cmp::max(cur_pos.0, max_x), - std::cmp::max(cur_pos.1, max_y), - ); - self.tiles_loop.push_back(DigTile { - position: cur_pos, - depth: 1, - color: i.color.to_owned(), - }); - } - } - // 2d vec dimension is (max_x+1, max_y+1) - for tile in &self.tiles_loop { - self.x_range.start = std::cmp::min(self.x_range.start, tile.position.0); - self.x_range.end = std::cmp::max(self.x_range.end, tile.position.0 + 1); - self.y_range.start = std::cmp::min(self.y_range.start, tile.position.1); - self.y_range.end = std::cmp::max(self.y_range.end, tile.position.1 + 1); - self.tiles_map.insert(tile.position, tile.clone()); - } - println!("{}", self); - self.fill_at(self.find_first_inside().unwrap()); - } - fn fill_at(&mut self, pos: Position) { - println!("filling at {:?}", pos); - let mut to_fill = vec![pos]; - - while let Some(fill_pos) = to_fill.pop() { - if self.tiles_map.contains_key(&fill_pos) { - continue; + let mut move_offset; + for (idx, i) in plan.instructions.iter().enumerate() { + let prev_instruction = if idx > 0 { + &plan.instructions[idx - 1] } else { - self.tiles_map.insert( - fill_pos, - DigTile { - position: fill_pos, - depth: 1, - color: "000000".to_owned(), - }, - ); - } - for new_pos in Direction::all() - .iter() - .map(|dir| self.pos_offset(fill_pos, dir.offset())) - .filter(|pos| !self.tiles_map.contains_key(&pos)) - { - to_fill.push(new_pos); - } + &plan.instructions[plan.instructions.len() - 1] + }; + let Some(next_instruction) = plan + .instructions + .get(idx + 1) + .or(Some(&plan.instructions[0])) + else { + panic!() + }; + let cur_turn = prev_instruction.dir.turn_kind(i.dir); + let next_turn = i.dir.turn_kind(next_instruction.dir); + // point needs to live on the 'outside' corner of the character. to + // achieve this we need to offset the move by the following + move_offset = match (cur_turn, next_turn) { + (Turn::RightNinety, Turn::RightNinety) => 1, + (Turn::RightNinety, Turn::LeftNinety) => 0, + (Turn::LeftNinety, Turn::LeftNinety) => -1, + (Turn::LeftNinety, Turn::RightNinety) => 0, + t => panic!("turn {:?} not allowed here", t), + }; + + cur_pos = self.pos_offset_n( + cur_pos, + i.dir.offset(), + (i.count as isize + move_offset) as usize, + ); + self.tiles_loop.push_back(DigTile { + position: cur_pos, + }); } - } - fn find_first_inside(&self) -> Option { - for y in self.y_range.clone() { - let mut inside = false; - for x in self.x_range.clone() { - if self.tiles_map.contains_key(&(x, y)) && (!self.tiles_map.contains_key(&(x - 1, y)) || !self.tiles_map.contains_key(&(x+1, y))) - { - inside = !inside; - } - if inside && !self.tiles_map.contains_key(&(x, y)) { - return Some((x as isize, y as isize)); - }; - } - } - None + + // Shoelace formula + // https://en.wikipedia.org/wiki/Shoelace_formula + let double_area: i64 = self + .tiles_loop + .iter() + .tuple_windows() + .map(|(a, b)| det(a.position, b.position)) + .sum(); + self.area = (double_area / 2).abs() as u64; } } @@ -266,16 +298,18 @@ impl From> for DigPlan { fn problem1(input: Lines) -> u64 { let plan = DigPlan::from(input); - println!("{:?}", plan); let mut dig = DigHole::new(); dig.run_plan(&plan); - println!("{}", dig); - dig.tiles_map.iter().count() as u64 + dig.area } // PROBLEM 2 solution fn problem2(input: Lines) -> u64 { - 0 + let mut plan = DigPlan::from(input); + let mut dig = DigHole::new(); + plan.part2_transform(); + dig.run_plan(&plan); + dig.area } #[cfg(test)] @@ -298,6 +332,17 @@ U 3 (#a77fa3) L 2 (#015232) U 2 (#7a21e3)"; + const AREA16_SQUARE: &str = &"R 3 (#000000) +D 3 (#000000) +L 3 (#000000) +U 4 (#000000"; + + #[test] + fn area16_square() { + let c = Cursor::new(AREA16_SQUARE); + assert_eq!(problem1(c.lines()), 16); + } + #[test] fn problem1_example() { let c = Cursor::new(EXAMPLE); @@ -307,6 +352,6 @@ U 2 (#7a21e3)"; #[test] fn problem2_example() { let c = Cursor::new(EXAMPLE); - assert_eq!(problem2(c.lines()), 0); + assert_eq!(problem2(c.lines()), 952408144115); } }