day 18 finsihed
This commit is contained in:
parent
75c7b08449
commit
7f5b6e03f9
4 changed files with 3161 additions and 1 deletions
257
src/days/day18/mod.rs
Normal file
257
src/days/day18/mod.rs
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
use itertools::Itertools;
|
||||
use std::{collections::HashSet, num::ParseIntError};
|
||||
use thiserror::Error;
|
||||
|
||||
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: &[String]) -> anyhow::Result<ResultType> {
|
||||
let blob = Blob::parse(lines)?;
|
||||
Ok(ResultType::Integer(blob.count_sides()))
|
||||
}
|
||||
|
||||
fn part2(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
let blob = Blob::parse(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 Blob {
|
||||
fn parse(lines: &[String]) -> Result<Self, DropletError> {
|
||||
let droplets = lines
|
||||
.iter()
|
||||
.map(|line| Droplet::parse(line))
|
||||
.try_collect()?;
|
||||
Ok(Blob { droplets })
|
||||
}
|
||||
|
||||
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_lines;
|
||||
use anyhow::Result;
|
||||
|
||||
#[test]
|
||||
fn test_part1() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(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_lines(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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue