1
0
Fork 0
2022/src/bin/day18.rs
Matej Focko 06243918fc
vectors: return value instead of reference
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>
2023-01-07 20:09:35 +01:00

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
)
}
}