advent-2022-rust/src/days/day18/mod.rs

262 lines
6.6 KiB
Rust

use itertools::Itertools;
use std::{collections::HashSet, num::ParseIntError};
use thiserror::Error;
use crate::common::file::split_lines;
use super::template::{DayTrait, ResultType};
const DAY_NUMBER: usize = 18;
pub struct Day;
impl DayTrait for Day {
fn get_day_number(&self) -> usize {
DAY_NUMBER
}
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
let blob = Blob::try_from(lines)?;
Ok(ResultType::Integer(blob.count_sides()))
}
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
let blob = Blob::try_from(lines)?;
Ok(ResultType::Integer(blob.count_outside()))
}
}
#[derive(Debug, Error)]
enum DropletError {
#[error("Illegal Droplet: {0}")]
IllegalDroplet(String),
#[error("no Integer")]
NoInteger(#[from] ParseIntError),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
struct Droplet(i64, i64, i64);
impl Droplet {
fn parse(line: &str) -> Result<Droplet, DropletError> {
let split = line.split(",").collect_vec();
if split.len() != 3 {
return Err(DropletError::IllegalDroplet(line.to_owned()));
}
let (x, y, z) = (split[0].parse()?, split[1].parse()?, split[2].parse()?);
Ok(Droplet(x, y, z))
}
pub fn neighbors(&self) -> Neighbors<'_> {
Neighbors::new(self)
}
}
struct Neighbors<'a>(&'a Droplet, usize);
impl<'a> Neighbors<'a> {
pub fn new(droplet: &'a Droplet) -> Neighbors<'a> {
Neighbors(droplet, 0)
}
}
impl<'a> Iterator for Neighbors<'a> {
type Item = Droplet;
fn next(&mut self) -> Option<Self::Item> {
self.1 += 1;
match self.1 {
1 => Some(Droplet(self.0 .0 - 1, self.0 .1, self.0 .2)),
2 => Some(Droplet(self.0 .0 + 1, self.0 .1, self.0 .2)),
3 => Some(Droplet(self.0 .0, self.0 .1 - 1, self.0 .2)),
4 => Some(Droplet(self.0 .0, self.0 .1 + 1, self.0 .2)),
5 => Some(Droplet(self.0 .0, self.0 .1, self.0 .2 - 1)),
6 => Some(Droplet(self.0 .0, self.0 .1, self.0 .2 + 1)),
_ => None,
}
}
}
struct Ranges {
x: (i64, i64),
y: (i64, i64),
z: (i64, i64),
}
impl Ranges {
pub fn new(droplet: &Droplet) -> Self {
Ranges {
x: (droplet.0, droplet.0),
y: (droplet.1, droplet.1),
z: (droplet.2, droplet.2),
}
}
pub fn contains(&self, droplet: &Droplet) -> bool {
self.x.0 <= droplet.0
&& droplet.0 <= self.x.1
&& self.y.0 <= droplet.1
&& droplet.1 <= self.y.1
&& self.z.0 <= droplet.2
&& droplet.2 <= self.z.1
}
pub fn grow(&mut self) {
self.x.0 -= 1;
self.x.1 += 1;
self.y.0 -= 1;
self.y.1 += 1;
self.z.0 -= 1;
self.z.1 += 1;
}
pub fn first(&self) -> Droplet {
Droplet(self.x.0, self.y.0, self.z.0)
}
pub fn extend(&mut self, droplet: &Droplet) {
self.x.0 = self.x.0.min(droplet.0);
self.x.1 = self.x.1.max(droplet.0);
self.y.0 = self.y.0.min(droplet.1);
self.y.1 = self.y.1.max(droplet.1);
self.z.0 = self.z.0.min(droplet.2);
self.z.1 = self.z.1.max(droplet.2);
}
#[allow(dead_code)]
pub fn volume(&self) -> i64 {
(self.x.1 - self.x.0 + 1) * (self.y.1 - self.y.0 + 1) * (self.z.1 - self.z.0 + 1)
}
}
struct Blob {
droplets: HashSet<Droplet>,
}
impl TryFrom<&str> for Blob {
type Error = DropletError;
fn try_from(lines: &str) -> Result<Self, Self::Error> {
let droplets = split_lines(lines)
.map(|line| Droplet::parse(line))
.try_collect()?;
Ok(Blob { droplets })
}
}
impl Blob {
fn extent(&self) -> Option<Ranges> {
if self.droplets.is_empty() {
return None;
}
let mut range = Ranges::new(&self.droplets.iter().next().unwrap());
for droplet in self.droplets.iter().skip(1) {
range.extend(droplet);
}
Some(range)
}
pub fn count_sides(&self) -> i64 {
let mut sides = 0;
let mut known = HashSet::new();
for droplet in &self.droplets {
sides += 6;
for neighbor in droplet.neighbors() {
if known.contains(&neighbor) {
sides -= 2;
}
}
known.insert(droplet);
}
sides
}
pub fn count_outside(&self) -> i64 {
let Some(mut extent)= self.extent() else {
return 0;
};
let mut sides = 0;
extent.grow();
let mut outer = HashSet::new();
let mut queue = Vec::new();
queue.push(extent.first());
while let Some(next) = queue.pop() {
for neighbor in next.neighbors() {
if extent.contains(&neighbor) && !outer.contains(&neighbor) {
if self.droplets.contains(&neighbor) {
sides += 1;
} else if !queue.contains(&neighbor) {
queue.push(neighbor);
}
}
}
outer.insert(next);
}
sides
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::common::file::read_string;
use anyhow::Result;
#[test]
fn test_part1() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?;
let expected = ResultType::Integer(64);
let result = day.part1(&lines)?;
assert_eq!(result, expected);
Ok(())
}
#[test]
fn test_part2() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?;
let expected = ResultType::Integer(58);
let result = day.part2(&lines)?;
assert_eq!(result, expected);
Ok(())
}
#[test]
fn parse() -> Result<()> {
let input = "1,2,3";
let expected = Droplet(1, 2, 3);
let result = Droplet::parse(input)?;
assert_eq!(result, expected);
Ok(())
}
#[test]
fn neighbors() {
let droplet = Droplet(1, 1, 1);
let expected = vec![
Droplet(0, 1, 1),
Droplet(2, 1, 1),
Droplet(1, 0, 1),
Droplet(1, 2, 1),
Droplet(1, 1, 0),
Droplet(1, 1, 2),
];
assert_eq!(droplet.neighbors().collect_vec(), expected);
}
#[test]
fn simple() {
let blob = Blob {
droplets: HashSet::from_iter(vec![Droplet(1, 1, 1), Droplet(1, 1, 2)]),
};
assert_eq!(blob.count_sides(), 10);
}
}