AOC 2022 day 18
This commit is contained in:
parent
1cc1da81e5
commit
0ca868fc1c
|
@ -0,0 +1 @@
|
||||||
|
/target
|
|
@ -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"
|
|
@ -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"
|
File diff suppressed because it is too large
Load Diff
|
@ -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(¤t).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]],
|
||||||
|
];
|
||||||
|
}
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
||||||
|
1,1,1
|
||||||
|
2,1,1
|
Loading…
Reference in New Issue