Solution for 2022/day18-part2

This commit is contained in:
Tobias Marschner 2024-04-01 18:38:43 +02:00
parent 7ffb5c4b11
commit 17b38905fb
2 changed files with 208 additions and 0 deletions

View File

@ -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]

View File

@ -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<String> = std::env::args().collect();
if args.len() < 2 {
panic!("Usage: ./main <input-file>\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::<usize>().unwrap())
.collect::<Vec<_>>();
[l[0], l[1], l[2]]
})
.collect::<Vec<_>>();
// 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::<usize>());
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
}