From 17b38905fb75cc322c6339222ce21123e2589eba Mon Sep 17 00:00:00 2001 From: Tobias Marschner Date: Mon, 1 Apr 2024 18:38:43 +0200 Subject: [PATCH] Solution for 2022/day18-part2 --- 2022/day18-part2/Cargo.toml | 8 ++ 2022/day18-part2/src/main.rs | 200 +++++++++++++++++++++++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 2022/day18-part2/Cargo.toml create mode 100644 2022/day18-part2/src/main.rs diff --git a/2022/day18-part2/Cargo.toml b/2022/day18-part2/Cargo.toml new file mode 100644 index 0000000..269a4ac --- /dev/null +++ b/2022/day18-part2/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "day18-part2" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/2022/day18-part2/src/main.rs b/2022/day18-part2/src/main.rs new file mode 100644 index 0000000..905d43b --- /dev/null +++ b/2022/day18-part2/src/main.rs @@ -0,0 +1,200 @@ +// Use a bespoke data structure for very fast access to the volume elements. +#[derive(Debug)] +struct Volume { + // First bool indicates lava, second bools indicates `visited`. + data: Vec<(bool, bool, usize)>, + dim: usize, +} + +impl Volume { + fn new(input: &Vec<[usize; 3]>) -> Volume { + // Determine the largest index across all three dimensions. + // Add 2 because ... + // (1) the maximum is an index, and we're looking to spec the size of the vector + // (2) we want some air surrounding the volume to easily spec a starting coordinate for the + // recursion and to ensure lava at the border of the coordinate system is counted + // correctly. + let dim = input.iter().flat_map(|a| a.iter()).max().unwrap() + 3; + + // Reserve the memory. + let mut v = Volume { + data: Vec::with_capacity(dim * dim * dim), + dim, + }; + + // Fill the vector with `false` values. + v.data.resize(dim * dim * dim, (false, false, 0)); + + // Now fill the volume with all the known data. + // Offset it by 1 to ensure it's surrounded by air on all sides. + for [x, y, z] in input { + *v.at_mut(*x + 1, *y + 1, *z + 1) = (true, false, 0); + } + + // Return the volume. + v + } + + fn at(&self, x: usize, y: usize, z: usize) -> &(bool, bool, usize) { + &self.data[z * self.dim * self.dim + y * self.dim + x] + } + + fn at_mut(&mut self, x: usize, y: usize, z: usize) -> &mut (bool, bool, usize) { + &mut self.data[z * self.dim * self.dim + y * self.dim + x] + } + + fn set_visited(&mut self, x: usize, y: usize, z: usize) { + self.at_mut(x, y, z).1 = true; + } + + fn is_visited(&self, x: usize, y: usize, z: usize) -> bool { + self.at(x, y, z).1 + } + + // Wrapper that allows invalid coordinates to simply return false. + // This is consistent with the logic of the task. + fn is_lava(&self, x: isize, y: isize, z: isize) -> bool { + if x < 0 + || y < 0 + || z < 0 + || x >= self.dim as isize + || y >= self.dim as isize + || z >= self.dim as isize + { + false + } else { + self.at(x as usize, y as usize, z as usize).0 + } + } + + #[allow(dead_code)] + fn print(&self) { + for z in 0..self.dim { + println!("\n\nLayer {}: \n", z); + for y in 0..self.dim { + if y % 5 == 0 { + println!(); + } + for x in 0..self.dim { + if x % 5 == 0 { + print!(" "); + } + let c = match self.at(x, y, z) { + (false, false, _) => '.', + (false, true, _) => '+', + (true, false, n) => (b'0' + (*n as u8)) as char, + (true, true, _) => '-', + }; + if c == '-' { + panic!("Oh no."); + } + print!("{}", c); + } + println!(); + } + } + } +} + +fn main() { + // Use command line arguments to specify the input filename. + let args: Vec = std::env::args().collect(); + if args.len() < 2 { + panic!("Usage: ./main \nNo input file provided. Exiting."); + } + + // Next, read the contents of the input file into a string for easier processing. + let input = std::fs::read_to_string(&args[1]).expect("Error opening file"); + + // Parse the input into a Vector of 3-tuples. + let input = input + .lines() + .map(|l| { + let l = l + .split(',') + .map(|e| e.parse::().unwrap()) + .collect::>(); + [l[0], l[1], l[2]] + }) + .collect::>(); + + // Create the more efficient data structure. + let mut v = Volume::new(&input); + + // // PART ONE + // // Count the surface-area of each cube. + // let mut surface_area = 0; + // for [x,y,z] in &input { + // // Add 6 to the total for each cube, but ... + // surface_area += 6; + // // ... remove one for any adjacent cube. + // let ix = *x as isize; + // let iy = *y as isize; + // let iz = *z as isize; + // for [dx,dy,dz] in [ + // [ix+1,iy,iz], + // [ix-1,iy,iz], + // [ix,iy+1,iz], + // [ix,iy-1,iz], + // [ix,iy,iz+1], + // [ix,iy,iz-1], + // ] { + // if v.is_lava(dx, dy, dz) { + // surface_area -= 1; + // } + // } + // } + // + // println!("Surface area: {}", surface_area); + + // PART TWO + // Call recursive evlauator on a point that is guaranteed\ + // to be air on the outside of the volume. + let air_coord = (v.dim - 1) as isize; + let ea = exterior_surface_area(&mut v, air_coord, air_coord, air_coord); + + // v.print(); + + // println!("Sum: {}", v.data.iter().map(|(_,_,n)| n).sum::()); + + println!("External surface area: {}", ea); +} + +fn exterior_surface_area(v: &mut Volume, x: isize, y: isize, z: isize) -> usize { + // We assume we're one a valid air-voxel. + // Mark this voxel as visited. + v.set_visited(x as usize, y as usize, z as usize); + // Iterate through all adjacent coordinates. + let mut c: usize = 0; + for [dx, dy, dz] in [ + [x + 1, y, z], + [x, y + 1, z], + [x, y, z + 1], + [x - 1, y, z], + [x, y - 1, z], + [x, y, z - 1], + ] { + // If the coordinates aren't valid, move on to the next voxel. + if dx < 0 + || dy < 0 + || dz < 0 + || dx >= v.dim as isize + || dy >= v.dim as isize + || dz >= v.dim as isize + { + continue; + } + // Is the target lava or air? + if v.is_lava(dx, dy, dz) { + // Count the surface. + c += 1; + v.at_mut(dx as usize, dy as usize, dz as usize).2 += 1; + } else { + // Air? Call recursively if it hasn't already been visited. + if !v.is_visited(dx as usize, dy as usize, dz as usize) { + c += exterior_surface_area(v, dx, dy, dz); + } + } + } + c +}