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 ) } }