diff --git a/2022/src/day18.rs b/2022/src/day18.rs index ee76db2..6ddc473 100644 --- a/2022/src/day18.rs +++ b/2022/src/day18.rs @@ -1,4 +1,6 @@ +use ahash::AHashMap; use ahash::AHashSet; +use anyhow::Context; use anyhow::Result; use nom::bytes::complete::tag; use nom::character::complete::newline; @@ -51,8 +53,81 @@ pub fn part1(input: &[u8]) -> Result { Ok(faces.to_string()) } -pub fn part2(_input: &[u8]) -> Result { - anyhow::bail!("not implemented") +pub fn part2(input: &[u8]) -> Result { + let voxels = parse_input(input, parse_voxels)?; + let max = voxels + .iter() + .copied() + .flatten() + .max() + .context("No voxels?")?; + + let mut outside = AHashMap::new(); + let mut outside_candidates = AHashSet::new(); + let mut todo = Vec::new(); + + let mut is_outside = |voxel: [u8; 3]| { + if let Some(&state) = outside.get(&voxel) { + return state; + } + + let mut is_outside = false; + + todo.push(voxel); + outside_candidates.insert(voxel); + + 'outer: while let Some(voxel) = todo.pop() { + for axis in 0..3 { + for offset in [-1, 1] { + let mut to_search = voxel; + if let Some(new_coord) = to_search[axis].checked_add_signed(offset) { + to_search[axis] = new_coord; + + if voxels.contains(&to_search) { + // Filled voxels cannot lead outside + continue; + } else if new_coord >= max { + is_outside = true; + break 'outer; + } else if let Some(&state) = outside.get(&to_search) { + is_outside = state; + break 'outer; + } else if outside_candidates.insert(to_search) { + todo.push(to_search); + } + } else { + // Managed to get below zero, which is outside + is_outside = true; + break 'outer; + } + } + } + } + + let map = |voxel| (voxel, is_outside); + + outside.extend(todo.drain(..).map(map)); + outside.extend(outside_candidates.drain().map(map)); + + is_outside + }; + + let mut faces = 0; + + for &voxel in &voxels { + for axis in 0..3 { + for offset in [-1, 1] { + let mut to_search = voxel; + to_search[axis] = to_search[axis].wrapping_add_signed(offset); + + if !voxels.contains(&to_search) && is_outside(to_search) { + faces += 1; + } + } + } + } + + Ok(faces.to_string()) } #[cfg(test)] @@ -65,4 +140,9 @@ mod tests { fn sample_part1() { assert_eq!(part1(SAMPLE).unwrap(), "64"); } + + #[test] + fn sample_part2() { + assert_eq!(part2(SAMPLE).unwrap(), "58"); + } }