AOC 2022 day 18

This commit is contained in:
Kienan Stewart 2022-12-18 18:42:24 -05:00
parent 1cc1da81e5
commit 0ca868fc1c
7 changed files with 2619 additions and 0 deletions

1
2022/18/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

77
2022/18/Cargo.lock generated Normal file
View File

@ -0,0 +1,77 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "common"
version = "0.1.0"
[[package]]
name = "day18"
version = "0.1.0"
dependencies = [
"common",
"ndarray",
]
[[package]]
name = "matrixmultiply"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "add85d4dd35074e6fedc608f8c8f513a3548619a9024b751949ef0e8e45a4d84"
dependencies = [
"rawpointer",
]
[[package]]
name = "ndarray"
version = "0.15.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32"
dependencies = [
"matrixmultiply",
"num-complex",
"num-integer",
"num-traits",
"rawpointer",
]
[[package]]
name = "num-complex"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19"
dependencies = [
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "rawpointer"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"

10
2022/18/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "day18"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
common = { path = "../common" }
ndarray = "0.15"

2150
2022/18/input Normal file

File diff suppressed because it is too large Load Diff

366
2022/18/src/main.rs Normal file
View File

@ -0,0 +1,366 @@
use std::str::FromStr;
fn main() {
let input_file = common::parse_args_input_file(&mut std::env::args());
let contents = std::fs::read_to_string(input_file).expect("Failed to read input file");
let mut space = std::collections::HashSet::<[i32; 3]>::new();
for line in contents.lines() {
if line.eq("") {
continue;
}
let mut index = 0;
let mut p = [0; 3];
for word in line.split(',') {
assert!(index < 3);
p[index] = i32::from_str(word).unwrap();
index += 1;
}
space.insert(p);
}
println!(
"[PART 1] {} open faces",
count_disjoint_sides(&space, std::vec::Vec::<std::collections::HashSet<[i32;3]>>::new()),
);
let part2 = count_part2(&space);
println!("[PART 2] {} open faces", part2);
}
fn count_part2(space: &std::collections::HashSet::<[i32; 3]>) -> u32 {
// To determine hollows, one approach might be to imagine the space as slices
// of the x,y axis starting from the highest Z value.
// For each slice we produce a list of possible hollow sets which are completely
// enclosed. It could be there are multiple pockets within a a given slice.
// Note: On the top and bottom slices, we can still include enclosed pockets
// despite the view that they are attached to "air" for the moment.
//
// As the slices of the droplet are iterated through, we can fuse potential
// pockets which are not disjoint.
//
// Once all the slices are iterated through we can run a validation step on
// each fused potential pocket.
// If the pocket has no disjoint sides with the union of itself and the droplet,
// it should be a proper pocket. If there is one or more disjoint sides, then
// "air" can move in to the space.
let mut max: [i32; 3] = [i32::MIN; 3];
let mut min: [i32; 3] = [i32::MAX; 3];
for point in space.iter() {
max = [
std::cmp::max(point[0], max[0]),
std::cmp::max(point[1], max[1]),
std::cmp::max(point[2], max[2]),
];
min = [
std::cmp::min(point[0], min[0]),
std::cmp::min(point[1], min[1]),
std::cmp::min(point[2], min[2]),
];
}
let mut potential_pockets = std::vec::Vec::<std::collections::HashSet<[i32; 3]>>::new();
let mut z = max[2];
// We'll take vertical slices starting a z_max, and each slice will be a grid
// from [x_min-1, y_min-1] to [x_max+1, y+max+1], so we have a bounding outer
// perimeter.
let x_size = (max[0] - min[0] + 1 + 2) as usize;
let y_size = (max[1] - min[1] + 1 + 2) as usize;
// For easing iterations, we offset create an offset such that [x_min-1, y_min-1] is now at [0, 0]
let mut offset: [i32; 2] = [
(min[0] - 1).abs() * if min[0] >= 0 { 1 } else { -1 },
(min[1] - 1).abs() * if min[1] >= 0 { 1 } else { -1 },
];
// println!("Obj {}x{} with offset: {:?}", x_size-3, y_size-3, offset);
while z >= min[2] {
// This is actually a place where I feel a bit stymied by rust's safety mechanisms.
// To create a multidimensional array I either have to do a Vec of Vecs, use someone's crate
// or drop into unsafe {} to allocate runtime determined byte arrays.
let mut plane = ndarray::Array2::<u32>::zeros((x_size, y_size));
// Fill in the plane data. If we were storing the underlying shape a 3d array we could
// avoid doing this work every time.
for x in 0..x_size {
for y in 0..y_size {
let p: [i32; 3] = [x as i32 - offset[0], y as i32 - offset[1], z];
if space.contains(&p) {
plane[[x, y]] = 1;
}
}
}
// Now we go over it again, and for each point that is 0,
// try to reach 0,0 (or really, any point on the perimiter)
for x in 1..x_size-1 {
for y in 1..y_size-1 {
if plane[[x, y]] == 1 {
continue;
}
// Choose the nearest perimiter point (although the path there
// is not necessarily the shorter path to any point on the
// perimiter. This seems silly, but helps save a lot of time
// pathing around to a fixed point like 0,0.
// distance left, right, up, down
let d = [x, x_size-x, y, y_size - y];
let mut perim_x = 0;
let mut perim_y = 0;
if d[2] < d[3] {
// We might want to go up, left, or right.
if d[2] <= d[0] && d[2] <= d[1] {
perim_x = x;
}
else {
perim_y = y;
if d[0] > d[1] {
perim_x = x_size-1;
}
}
}
else {
// We might want to go down, left, or right.
if d[3] <= d[0] && d[3] <= d[1] {
perim_x = x;
perim_y = y_size-1;
}
else {
perim_y = y;
if d[0] > d[1] {
perim_x = x_size-1;
}
}
}
if !can_path_to(&plane, [x, y], [perim_x, perim_y]) {
let mut pocket = std::collections::HashSet::<[i32; 3]>::new();
let point = [x as i32 - offset[0], y as i32 - offset[1], z];
pocket.insert(point);
potential_pockets.push(pocket);
}
}
}
// Fuse potential pockets.
let mut index = 0;
let mut pockets_length = potential_pockets.len();
while index < pockets_length {
if index + 1 > pockets_length {
break;
}
let mut index2 = index + 1;
let mut fused = false;
while index2 < pockets_length {
// If any of the points pocketN and pocketN+1 share a face,
// we can fuse them.
let mut fusable = false;
for point1 in potential_pockets[index].iter() {
for neighbour in neighbours(&point1) {
if potential_pockets[index2].contains(&neighbour) {
fusable = true;
break;
}
}
}
if fusable {
// println!("Merging these two pockets:\n{:?}\n{:?}", potential_pockets[index], potential_pockets[index2]);
// Fuse these two, then re-run the iteration
// println!("Fused {} and {}", index, index2);
let pocket2 = potential_pockets[index2].clone();
for p in pocket2.iter() {
potential_pockets[index].insert(*p);
}
potential_pockets.remove(index2);
pockets_length = potential_pockets.len();
fused = true;
continue;
}
index2 += 1;
}
if fused {
// Start the process over.
index = 0;
}
index += 1;
}
let mut debug = false;
if debug {
println!("Plane at z {}", z);
let mut x = 0;
for row in plane.rows() {
let mut s = String::new();
let mut y = 0;
for value in row.iter() {
if *value == 0 {
// Is it in pockets?
let mut c = '.';
let mut pocket_index = 0;
for pocket in potential_pockets.iter() {
let point = [x as i32 - offset[0], y as i32 - offset[1], z];
if pocket.contains(&point) {
// If potential pockets is less than 52, we'll use the range
// of a-zA-Z to differentiate them
c = pocket_char(pocket_index as u32);
break;
}
pocket_index += 1;
}
s.push(c);
}
else {
s.push('#');
}
y += 1;
}
x += 1;
println!("{}", s);
}
println!("");
}
z -= 1;
}
// println!("{} discrete potential pockets", potential_pockets.len());
// For each discrete potential pocket, we need to ensure that it's not
// actually open to space at some point. (Aka, it's fully enclosed).
let mut pocket_index = 0;
let mut pockets_removed = 0;
while pocket_index < potential_pockets.len() {
let open_spaces = count_disjoint_sides(
&potential_pockets[pocket_index],
vec![space.clone()],
);
// println!(
// "Pocket '{}' ({}) has {} open faces",
// pocket_char((pocket_index + pockets_removed) as u32),
// pocket_index + pockets_removed, open_spaces
// );
if open_spaces > 0 {
potential_pockets.remove(pocket_index);
pockets_removed += 1;
continue;
}
pocket_index += 1;
}
// println!("{} pockets removed after verifying if they were open to air or not", potential_pockets.len());
return count_disjoint_sides(
space,
potential_pockets,
);
}
fn pocket_char(v: u32) -> char {
if v < 26 {
return char::from_u32(v + 97).unwrap();
}
else if v < 52 {
return char::from_u32(v + 65 - 26).unwrap();
}
return '*';
}
fn can_path_to(plane: &ndarray::Array<u32, ndarray::Ix2>, start: [usize; 2], end: [usize; 2]) -> bool {
let (x_max, y_max) = plane.dim();
// A*
let mut open_set = std::collections::VecDeque::<[usize; 2]>::new();
let mut came_from = std::collections::HashMap::<[usize; 2], [usize; 2]>::new();
let mut scores = std::collections::HashMap::<[usize; 2], u32>::new();
let mut estimates = std::collections::HashMap::<[usize; 2], u32>::new();
open_set.push_back(start);
scores.insert(start, 0);
while open_set.len() > 0 {
let current = get_next_node_to_test(&open_set, &estimates);
if current == end {
return true;
}
open_set.retain(|&x| x != current);
let mut neighbours = std::vec::Vec::<[usize; 2]>::new();
// Can go left
if current[0] > 0 {
if plane[[current[0]-1, current[1]]] == 0 {
neighbours.push([current[0]-1, current[1]]);
}
}
// Right
if current[0] + 1 < x_max {
if plane[[current[0]+1, current[1]]] == 0 {
neighbours.push([current[0]+1, current[1]]);
}
}
// Up
if current[1] > 0 {
if plane[[current[0], current[1]-1]] == 0 {
neighbours.push([current[0], current[1]-1]);
}
}
// Down
if current[1] + 1 < y_max {
if plane[[current[0], current[1]+1]] == 0 {
neighbours.push([current[0], current[1]+1]);
}
}
for neighbour in neighbours.iter() {
let score = scores.get(&current).unwrap() + 1;
let neighbour_score = match scores.get(neighbour) {
None => { u32::MAX },
Some(v) => { *v },
};
if score < neighbour_score {
came_from.insert(*neighbour, current);
scores.insert(*neighbour, score);
estimates.insert(*neighbour, score + 10);
if !open_set.contains(&neighbour) {
open_set.push_back(*neighbour);
}
}
}
}
return false;
}
fn get_next_node_to_test(open: &std::collections::VecDeque<[usize; 2]>, estimates: &std::collections::HashMap<[usize; 2], u32>) -> [usize; 2] {
let mut current: Option<[usize; 2]> = None;
let mut current_value = u32::MAX;
for n in open.iter() {
let score = match estimates.get(n) {
None => { u32::MAX },
Some(v) => { *v },
};
if current.is_none() || score < current_value {
current = Some(*n);
current_value = score;
}
}
return current.unwrap();
}
fn count_disjoint_sides(space: &std::collections::HashSet::<[i32; 3]>, ignore_sets: std::vec::Vec<std::collections::HashSet<[i32; 3]>>) -> u32 {
let mut count = 0;
for point in space.iter() {
for tp in neighbours(point) {
let mut ignore = false;
for set in ignore_sets.iter() {
if set.contains(&tp) {
ignore = true;
break;
}
}
if !ignore && !space.contains(&tp) {
count += 1;
}
}
}
return count;
}
fn neighbours(point: &[i32; 3]) -> [[i32; 3]; 6] {
return [
[point[0], point[1], point[2]+1],
[point[0], point[1], point[2]-1],
[point[0], point[1]+1, point[2]],
[point[0], point[1]-1, point[2]],
[point[0]+1, point[1], point[2]],
[point[0]-1, point[1], point[2]],
];
}

13
2022/18/test_input Normal file
View File

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

2
2022/18/test_input2 Normal file
View File

@ -0,0 +1,2 @@
1,1,1
2,1,1