diff --git a/samples/day18.txt b/samples/day18.txt new file mode 100644 index 0000000..d18bf98 --- /dev/null +++ b/samples/day18.txt @@ -0,0 +1,13 @@ +2,2,2 +1,2,2 +3,2,2 +2,1,2 +2,3,2 +2,2,1 +2,2,3 +2,2,4 +2,2,6 +1,2,5 +3,2,5 +2,1,5 +2,3,5 \ No newline at end of file diff --git a/src/bin/day18.rs b/src/bin/day18.rs new file mode 100644 index 0000000..f813393 --- /dev/null +++ b/src/bin/day18.rs @@ -0,0 +1,186 @@ +use std::cmp::{max, min}; +use std::collections::{BTreeMap, BTreeSet, VecDeque}; + +use aoc_2022::*; + +type Coord = Vector3D; + +type Input = Vec; +type Output = usize; + +lazy_static! { + static ref TOUCHING: Vec = vec![-1, 1] + .iter() + .flat_map(|&d| vec![ + Coord::new(d, 0, 0), + Coord::new(0, d, 0), + Coord::new(0, 0, d) + ]) + .collect_vec(); +} + +fn get_bounding_box(droplets: &[Coord]) -> (Coord, Coord) { + let min_coord = + droplets + .iter() + .fold(Coord::new(i32::MAX, i32::MAX, i32::MAX), |acc, &droplet| { + Coord::new( + min(*acc.x(), *droplet.x()), + min(*acc.y(), *droplet.y()), + min(*acc.z(), *droplet.z()), + ) + }); + let max_coord = + droplets + .iter() + .fold(Coord::new(i32::MIN, i32::MIN, i32::MIN), |acc, &droplet| { + Coord::new( + max(*acc.x(), *droplet.x()), + max(*acc.y(), *droplet.y()), + max(*acc.z(), *droplet.z()), + ) + }); + + (min_coord, max_coord) +} + +fn _check_bounding_box(droplets: &[Coord]) { + let (min_coord, max_coord) = get_bounding_box(droplets); + + debug!("Bounding box: {:?} <-> {:?}", min_coord, max_coord); + debug!( + "Cubes in a bounding box: {:?}", + (*max_coord.x() - *min_coord.x() + 1) + * (*max_coord.y() - *min_coord.y() + 1) + * (*max_coord.z() - *min_coord.z() + 1) + ); +} + +#[derive(Debug, Clone, PartialEq)] +enum Cube { + Air, + Lava, + Obsidian, +} + +fn aerate(space: &mut BTreeMap, start: Coord) { + let mut queue = VecDeque::::new(); + queue.push_back(start); + space.insert(start, Cube::Air); + + while let Some(pos) = queue.pop_front() { + for neighbour in TOUCHING + .iter() + .map(|&d| d + pos) + .filter(|pos| *space.get(pos).unwrap_or(&Cube::Air) == Cube::Lava) + .collect_vec() + { + space.insert(neighbour, Cube::Air); + queue.push_back(neighbour); + } + } +} + +struct Day18; +impl Solution for Day18 { + fn parse_input>(pathname: P) -> Input { + file_to_lines(pathname) + .iter() + .map(|l| { + let coords = l.split(',').map(|c| c.parse().unwrap()).collect_vec(); + + Coord::new(coords[0], coords[1], coords[2]) + }) + .collect_vec() + } + + fn part_1(input: &Input) -> Output { + let mut surface = 0; + + let mut droplets = BTreeSet::::new(); + for &droplet in input { + surface += 6; + + for _touching in TOUCHING + .iter() + .map(|&d| d + droplet) + .filter(|d| droplets.contains(d)) + { + surface -= 2; + } + + droplets.insert(droplet); + } + + surface + } + + fn part_2(input: &Input) -> Output { + let mut space = BTreeMap::::new(); + + _check_bounding_box(input); // for debugging purposes + let (min_coords, max_coords) = get_bounding_box(input); + + // lava everywhere + for cube in [ + *min_coords.x()..=*max_coords.x(), + *min_coords.y()..=*max_coords.y(), + *min_coords.z()..=*max_coords.z(), + ] + .into_iter() + .multi_cartesian_product() + .map(|coords| { + let (x, y, z) = coords + .into_iter() + .collect_tuple::<(i32, i32, i32)>() + .unwrap(); + + Coord::new(x, y, z) + }) { + space.insert(cube, Cube::Lava); + } + + // override with obsidian + for &droplet in input { + space.insert(droplet, Cube::Obsidian); + } + + // update the outside with the air using a BFS + aerate( + &mut space, + Coord::new(*min_coords.x(), *min_coords.y(), *min_coords.z()), + ); + + // count the faces touching the air + let mut surface = 0; + for &droplet in input { + surface += TOUCHING + .iter() + .map(|&d| space.get(&(droplet + d)).unwrap_or(&Cube::Air)) + .filter(|&cube| *cube == Cube::Air) + .count(); + } + + surface + } +} + +fn main() -> Result<()> { + // Day18::run("sample") + Day18::main() +} + +test_sample!(day_18, Day18, 64, 58); + +#[cfg(test)] +mod day_18_additional_tests { + use super::*; + + #[test] + fn test_small_example() { + assert_eq!( + Day18::part_1(&vec![Vector3D::new(1, 1, 1), Vector3D::new(2, 1, 1)]), + 10 + ) + } +}