Matej Focko
06243918fc
There was no use-case for returning reference rather than a value and in majority of case the dereference was needed. When using 2D/3D vectors, they are mostly initialized with the numbers and it is contraproductive to pass around references to numbers as they are more expensive than the copies. Signed-off-by: Matej Focko <me@mfocko.xyz>
186 lines
4.7 KiB
Rust
186 lines
4.7 KiB
Rust
use std::cmp::{max, min};
|
|
use std::collections::{BTreeMap, BTreeSet, VecDeque};
|
|
|
|
use aoc_2022::*;
|
|
|
|
type Coord = Vector3D<i32>;
|
|
|
|
type Input = Vec<Coord>;
|
|
type Output = usize;
|
|
|
|
lazy_static! {
|
|
static ref TOUCHING: Vec<Coord> = 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<Coord, Cube>, start: Coord) {
|
|
let mut queue = VecDeque::<Coord>::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<Input, Output> for Day18 {
|
|
fn parse_input<P: AsRef<Path>>(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::<Coord>::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::<Coord, Cube>::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
|
|
)
|
|
}
|
|
}
|