diff --git a/day-16/Cargo.toml b/day-16/Cargo.toml index fc16800..64a7c21 100644 --- a/day-16/Cargo.toml +++ b/day-16/Cargo.toml @@ -6,6 +6,8 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +rayon.workspace = true + itertools = { workspace = true } nom = { workspace = true } tracing = { workspace = true } @@ -14,7 +16,6 @@ miette = { workspace = true } thiserror = { workspace = true } dhat = { workspace = true } glam = { workspace = true } - [dev-dependencies] divan = { workspace = true } env_logger = { workspace = true } diff --git a/day-16/src/part1.rs b/day-16/src/part1.rs index e012a03..09ddee6 100644 --- a/day-16/src/part1.rs +++ b/day-16/src/part1.rs @@ -55,7 +55,7 @@ fn parse(input: &str) -> IResult<&str, HashMap> { Ok((input, grid)) } -fn step(grid: &HashMap, beams: &Vec) -> Vec { +fn step(grid: &HashMap, beams: &[Beam]) -> Vec { beams .iter() .flat_map(|beam| { @@ -146,7 +146,7 @@ fn step(grid: &HashMap, beams: &Vec) -> Vec { .collect_vec() } -fn check_bounds(_grid: &HashMap, beams: &Vec, boundary: &IVec2) -> Vec { +fn check_bounds(_grid: &HashMap, beams: &[Beam], boundary: &IVec2) -> Vec { beams .iter() .filter(|beam| { @@ -159,7 +159,7 @@ fn check_bounds(_grid: &HashMap, beams: &Vec, boundary: &IVec .collect_vec() } -fn check_history(beams: &Vec, visited: &HashSet) -> Vec { +fn check_history(beams: &[Beam], visited: &HashSet) -> Vec { beams .iter() .filter(|beam| !visited.contains(beam)) @@ -167,6 +167,7 @@ fn check_history(beams: &Vec, visited: &HashSet) -> Vec { .collect_vec() } +#[cfg(test)] fn visualize(grid: &HashMap, visited: &HashSet) { let boundary = grid.keys().fold(IVec2::new(0, 0), |max, position| { IVec2::new(max.x.max(position.x), max.y.max(position.y)) diff --git a/day-16/src/part2.rs b/day-16/src/part2.rs index 1ff3976..d485fab 100644 --- a/day-16/src/part2.rs +++ b/day-16/src/part2.rs @@ -1,8 +1,235 @@ use crate::error::AocError; +use glam::IVec2; +use itertools::Itertools; +use nom::bytes::complete::is_a; +use nom::character::complete::line_ending; +use nom::multi::separated_list1; +use nom::IResult; +use rayon::prelude::*; +use std::collections::HashMap; +use std::collections::HashSet; + +#[derive(Debug, Clone)] +enum Tile { + Empty, + RightMirror, + LeftMirror, + HorizontalSplit, + VerticalSplit, +} +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +enum Direction { + North, + South, + East, + West, +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +struct Beam { + direction: Direction, + position: IVec2, +} + +fn parse(input: &str) -> IResult<&str, HashMap> { + let (input, rows) = separated_list1(line_ending, is_a(r".|\/-"))(input)?; + + let grid = rows + .into_iter() + .enumerate() + .flat_map(|(y, row)| { + row.chars().enumerate().map(move |(x, tile)| { + let position = IVec2::new(x as i32, y as i32); + let tile = match tile { + '.' => Tile::Empty, + '|' => Tile::VerticalSplit, + '/' => Tile::RightMirror, + '\\' => Tile::LeftMirror, + '-' => Tile::HorizontalSplit, + _ => unreachable!(), + }; + (position, tile) + }) + }) + .collect::>(); + + Ok((input, grid)) +} + +fn step(grid: &HashMap, beams: &[Beam]) -> Vec { + beams + .iter() + .flat_map(|beam| { + let position = match beam.direction { + Direction::North => beam.position + IVec2::new(0, -1), + Direction::South => beam.position + IVec2::new(0, 1), + Direction::East => beam.position + IVec2::new(1, 0), + Direction::West => beam.position + IVec2::new(-1, 0), + }; + + match grid.get(&position) { + Some(Tile::VerticalSplit) => match beam.direction { + Direction::North | Direction::South => vec![Beam { + direction: beam.direction.clone(), + position, + }], + Direction::East | Direction::West => vec![ + Beam { + direction: Direction::North, + position, + }, + Beam { + direction: Direction::South, + position, + }, + ], + }, + + Some(Tile::HorizontalSplit) => match beam.direction { + Direction::East | Direction::West => vec![Beam { + direction: beam.direction.clone(), + position, + }], + Direction::North | Direction::South => vec![ + Beam { + direction: Direction::East, + position, + }, + Beam { + direction: Direction::West, + position, + }, + ], + }, + Some(Tile::RightMirror) => match beam.direction { + Direction::North => vec![Beam { + direction: Direction::East, + position, + }], + Direction::South => vec![Beam { + direction: Direction::West, + position, + }], + Direction::East => vec![Beam { + direction: Direction::North, + position, + }], + Direction::West => vec![Beam { + direction: Direction::South, + position, + }], + }, + Some(Tile::LeftMirror) => match beam.direction { + Direction::North => vec![Beam { + direction: Direction::West, + position, + }], + Direction::South => vec![Beam { + direction: Direction::East, + position, + }], + Direction::East => vec![Beam { + direction: Direction::South, + position, + }], + Direction::West => vec![Beam { + direction: Direction::North, + position, + }], + }, + Some(Tile::Empty) | None => vec![Beam { + direction: beam.direction.clone(), + position, + }], + } + .to_vec() + }) + .collect_vec() +} + +fn check_bounds(_grid: &HashMap, beams: &[Beam], boundary: &IVec2) -> Vec { + beams + .iter() + .filter(|beam| { + beam.position.x <= boundary.x + && beam.position.y <= boundary.y + && beam.position.x >= 0 + && beam.position.y >= 0 + }) + .cloned() + .collect_vec() +} + +fn check_history(beams: &[Beam], visited: &HashSet) -> Vec { + beams + .iter() + .filter(|beam| !visited.contains(beam)) + .cloned() + .collect_vec() +} + +fn process_from(starting_position: &Beam, grid: &HashMap, boundary: &IVec2) -> u64 { + let mut visited: HashSet = HashSet::new(); + + let mut beams: Vec = vec![starting_position.clone()]; + + loop { + beams = step(grid, &beams); + beams = check_bounds(grid, &beams, boundary); + beams = check_history(&beams, &visited); + visited.extend(beams.iter().cloned()); + + if beams.is_empty() { + break; + } + } + + let energized = visited + .iter() + .map(|node| node.position) + .collect::>(); + + energized.len() as u64 +} #[tracing::instrument] pub fn process(input: &str) -> miette::Result { - Ok(0) + let (input, grid) = parse(input).unwrap(); + debug_assert!(input.is_empty(), "Not all input was parsed"); + + let boundary = grid.keys().fold(IVec2::new(0, 0), |max, position| { + IVec2::new(max.x.max(position.x), max.y.max(position.y)) + }); + + let mut starting_positions = vec![]; + for y in 0..=boundary.y { + starting_positions.push(Beam { + position: IVec2::new(-1, y), + direction: Direction::East, + }); + starting_positions.push(Beam { + position: IVec2::new(boundary.x + 1, y), + direction: Direction::West, + }); + } + for x in 0..boundary.x { + starting_positions.push(Beam { + position: IVec2::new(x, -1), + direction: Direction::South, + }); + starting_positions.push(Beam { + position: IVec2::new(x, boundary.y + 1), + direction: Direction::North, + }); + } + + let energized: u64 = starting_positions + .par_iter() + .map(|s| process_from(s, &grid, &boundary)) + .max() + .unwrap(); + + Ok(energized) } #[cfg(test)] @@ -12,7 +239,7 @@ mod tests { #[test_log::test] fn test_process() -> miette::Result<()> { let input = include_str!("../test-input.txt"); - assert_eq!(0, process(input)?); + assert_eq!(51, process(input)?); Ok(()) } }