262 lines
6.6 KiB
Rust
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);
|
|
}
|
|
}
|