diff --git a/2023/src/bin/day10.rs b/2023/src/bin/day10.rs index c394b5b..6aabaf8 100644 --- a/2023/src/bin/day10.rs +++ b/2023/src/bin/day10.rs @@ -1,6 +1,6 @@ #![feature(test)] use core::panic; -use std::collections::HashMap; +use std::collections::{HashMap, VecDeque}; use anyhow::Result; use aoc::Solver; @@ -180,46 +180,51 @@ impl Maze { } } -// #[derive(Debug, PartialEq, Eq, Clone, Copy)] -// enum Loop { -// North, -// East, -// South, -// West, -// Inner, -// } -// -// impl Loop { -// fn from(direction: (isize, isize)) -> Self { -// match direction { -// (0, -1) => Self::North, -// (1, 0) => Self::East, -// (0, 1) => Self::South, -// (-1, 0) => Self::West, -// _ => unreachable!("Invalid direction"), -// } -// } -// -// fn offset(&self) -> (isize, isize) { -// match self { -// Loop::North => (0, -1), -// Loop::East => (1, 0), -// Loop::South => (0, 1), -// Loop::West => (-1, 0), -// Loop::Inner => (0, 0), -// } -// } -// } -// -// fn flood_fill(position: (isize, isize), map: &mut HashMap<(isize, isize), Loop>) { -// if map.get(&position).is_none() { -// map.insert(position, Loop::Inner); -// -// for direction in DIRECTIONS { -// flood_fill((position.0 + direction.0, position.1 + direction.1), map) -// } -// } -// } +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +enum Loop { + North, + East, + South, + West, + Inner, +} + +impl Loop { + fn from(direction: (isize, isize)) -> Self { + match direction { + (0, -1) => Self::North, + (1, 0) => Self::East, + (0, 1) => Self::South, + (-1, 0) => Self::West, + _ => unreachable!("Invalid direction"), + } + } + + fn offset(&self) -> (isize, isize) { + match self { + Loop::North => (0, -1), + Loop::East => (1, 0), + Loop::South => (0, 1), + Loop::West => (-1, 0), + Loop::Inner => (0, 0), + } + } +} + +fn flood_fill(position: (isize, isize), map: &mut HashMap<(isize, isize), Loop>) { + let mut queue = VecDeque::new(); + queue.push_back(position); + + while let Some(position) = queue.pop_front() { + if map.get(&position).is_none() { + map.insert(position, Loop::Inner); + + for direction in DIRECTIONS { + queue.push_back((position.0 + direction.0, position.1 + direction.1)); + } + } + } +} static DIRECTIONS: [(isize, isize); 4] = [(0, -1), (1, 0), (0, 1), (-1, 0)]; @@ -292,10 +297,11 @@ impl aoc::Solver for Day { } } + // Alternate solution that works for the examples and is off-by-one for the actual input fn part2(input: &str) -> Self::Output2 { let mut position_start = (-1, -1); - - let maze: HashMap<(isize, isize), Maze> = input + let end_direction; + let mut maze: HashMap<(isize, isize), Maze> = input .lines() .enumerate() .flat_map(|(y, line)| { @@ -311,11 +317,13 @@ impl aoc::Solver for Day { }) .collect(); + let mut map = HashMap::new(); + if position_start == (-1, -1) { panic!("No valid start in input"); } - let mut map = HashMap::<(isize, isize), Maze>::new(); + let mut sides = (0, 0); let mut position_current = position_start; let mut position_previous = position_start; @@ -335,12 +343,17 @@ impl aoc::Solver for Day { if let Some(tile_next) = maze.get(&position_next) { if tile_current.connects(tile_next, direction) { - map.insert(position_current, *tile_current); + map.insert(position_current, Loop::from(direction)); position_previous = position_current; position_current = position_next; + let s = tile_current.sides(direction); + sides.0 += s.0; + sides.1 += s.1; + if position_current == position_start { + end_direction = Loop::from(direction); break 'outer; } @@ -352,165 +365,151 @@ impl aoc::Solver for Day { unreachable!("Unable to move forward..."); } - // It appears that the start can always be replaced with an F - // Atleast in all the examples and my input - map.insert(position_start, Maze::SouthEast); + // Replace the start with the correct piece + let start = match (end_direction, map.get(&position_start).unwrap()) { + (Loop::North, Loop::North) | (Loop::South, Loop::South) => Maze::Vertical, + (Loop::East, Loop::East) | (Loop::West, Loop::West) => Maze::Horizontal, + (Loop::South, Loop::East) | (Loop::West, Loop::North) => Maze::NorthEast, + (Loop::South, Loop::West) | (Loop::East, Loop::North) => Maze::NorthWest, + (Loop::East, Loop::South) | (Loop::North, Loop::West) => Maze::SouthWest, + (Loop::North, Loop::East) | (Loop::West, Loop::South) => Maze::SouthEast, + _ => unreachable!(), + }; + maze.insert(position_start, start); - let bounds = ( - input.lines().map(|line| line.len()).max().unwrap(), - input.lines().count(), - ); + // Add the sides of the start + let start_sides = start.sides(map.get(&position_start).unwrap().offset()); + sides.0 += start_sides.0; + sides.1 += start_sides.1; - let mut count = 0; - let mut in_loop = false; - let mut maybe = None; - for y in 0..bounds.1 { - for x in 0..bounds.0 { - if let Some(entry) = map.get(&(x as isize, y as isize)) { - match entry { - Maze::Vertical => in_loop = !in_loop, - Maze::Horizontal => {} - dir @ (Maze::NorthEast | Maze::SouthEast) => maybe = Some(*dir), - Maze::NorthWest => { - if maybe == Some(Maze::SouthEast) { - in_loop = !in_loop - } + // Determine if the loop was travelled clockwise or anti clockwise + let clockwise = sides.0 > sides.1; + + let mut position_current = position_start; + loop { + let &direction = map.get(&position_current).unwrap(); + let tile = maze.get(&position_current).unwrap(); + + // Get neighbouring tiles that are inside of the loop + let checks = match direction { + Loop::North => match tile { + Maze::Vertical => { + if clockwise { + vec![(1, 0)] + } else { + vec![(-1, 0)] } - Maze::SouthWest => { - if maybe == Some(Maze::NorthEast) { - in_loop = !in_loop - } - } - Maze::Start => unreachable!("Start should have been replaced"), } - } else if in_loop { - count += 1; - } + Maze::NorthEast => { + if clockwise { + vec![] + } else { + vec![(-1, 0), (0, 1)] + } + } + Maze::NorthWest => { + if clockwise { + vec![(1, 0), (0, 1)] + } else { + vec![] + } + } + _ => unreachable!(), + }, + Loop::East => match tile { + Maze::Horizontal => { + if clockwise { + vec![(0, 1)] + } else { + vec![(0, -1)] + } + } + Maze::NorthEast => { + if clockwise { + vec![(-1, 0), (0, 1)] + } else { + vec![] + } + } + Maze::SouthEast => { + if clockwise { + vec![] + } else { + vec![(-1, 0), (0, -1)] + } + } + _ => unreachable!(), + }, + Loop::South => match tile { + Maze::Vertical => { + if clockwise { + vec![(-1, 0)] + } else { + vec![(1, 0)] + } + } + Maze::SouthWest => { + if clockwise { + vec![] + } else { + vec![(0, -1), (1, 0)] + } + } + Maze::SouthEast => { + if clockwise { + vec![(0, -1), (-1, 0)] + } else { + vec![] + } + } + _ => unreachable!(), + }, + Loop::West => match tile { + Maze::Horizontal => { + if clockwise { + vec![(0, -1)] + } else { + vec![(0, 1)] + } + } + Maze::NorthWest => { + if clockwise { + vec![] + } else { + vec![(1, 0), (0, 1)] + } + } + Maze::SouthWest => { + if clockwise { + vec![(1, 0), (0, -1)] + } else { + vec![] + } + } + _ => unreachable!(), + }, + Loop::Inner => unreachable!("Loop should not contain Inner"), + }; + + // Perform a floodfill from those neighbouring tiles + for check in checks { + let check = (position_current.0 + check.0, position_current.1 + check.1); + flood_fill(check, &mut map); + } + + let direction = direction.offset(); + + position_current.0 += direction.0; + position_current.1 += direction.1; + + if position_current == position_start { + break; } } - count + // Count how many tiles are marked as inner + map.iter() + .filter(|(_, &value)| value == Loop::Inner) + .count() } - - // Alternate solution that works for the examples and is off-by-one for the actual input - // fn part2(input: &str) -> Self::Output2 { - // let mut position_start = (-1, -1); - // let maze: HashMap<(isize, isize), Maze> = input - // .lines() - // .enumerate() - // .flat_map(|(y, line)| { - // line.chars() - // .enumerate() - // .filter_map(|(x, c)| Maze::from(c).map(|maze| ((x as isize, y as isize), maze))) - // .inspect(|(position, maze)| { - // if *maze == Maze::Start { - // position_start = *position - // } - // }) - // .collect::>() - // }) - // .collect(); - // - // let mut map = HashMap::<(isize, isize), Loop>::new(); - // - // if position_start == (-1, -1) { - // panic!("No valid start in input"); - // } - // - // let mut sides = (0, 0); - // - // let mut position_current = position_start; - // let mut position_previous = position_start; - // 'outer: loop { - // let tile_current = maze.get(&position_current).unwrap(); - // - // for direction in DIRECTIONS { - // let position_next = ( - // position_current.0 + direction.0, - // position_current.1 + direction.1, - // ); - // - // // We are now allowed to go back to the previous position - // if position_next == position_previous { - // continue; - // } - // - // if let Some(tile_next) = maze.get(&position_next) { - // if tile_current.connects(tile_next, direction) { - // map.insert(position_current, Loop::from(direction)); - // - // position_previous = position_current; - // position_current = position_next; - // - // let s = tile_current.sides(direction); - // sides.0 += s.0; - // sides.1 += s.1; - // - // if position_current == position_start { - // break 'outer; - // } - // - // continue 'outer; - // } - // } - // } - // - // unreachable!("Unable to move forward..."); - // } - // - // let clockwise = sides.0 > sides.1; - // - // let mut position_current = position_start; - // loop { - // let &direction = map.get(&position_current).unwrap(); - // - // let check = match direction { - // Loop::North => { - // if clockwise { - // (1, 0) - // } else { - // (-1, 0) - // } - // } - // Loop::East => { - // if clockwise { - // (0, 1) - // } else { - // (0, -1) - // } - // } - // Loop::South => { - // if clockwise { - // (-1, 0) - // } else { - // (1, 0) - // } - // } - // Loop::West => { - // if clockwise { - // (0, -1) - // } else { - // (0, 1) - // } - // } - // Loop::Inner => unreachable!("Loop should not contain Inner"), - // }; - // let check = (position_current.0 + check.0, position_current.1 + check.1); - // - // flood_fill(check, &mut map); - // - // let direction = direction.offset(); - // - // position_current.0 += direction.0; - // position_current.1 += direction.1; - // - // if position_current == position_start { - // break; - // } - // } - // - // map.iter() - // .filter(|(_, &value)| value == Loop::Inner) - // .count() - // } }