Cleanup for 2022 days 11 through 21: Turned into a lib and introduced parse errors
This commit is contained in:
parent
c7852c9791
commit
fcb2fed515
26 changed files with 1095 additions and 879 deletions
123
2022/day18-boiling_boulders/src/lib.rs
Normal file
123
2022/day18-boiling_boulders/src/lib.rs
Normal file
|
@ -0,0 +1,123 @@
|
|||
use core::fmt::Display;
|
||||
use std::{num::ParseIntError, collections::BTreeSet};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ParseError<'a> {
|
||||
ParseIntError(std::num::ParseIntError),
|
||||
LineMalformed(&'a str),
|
||||
}
|
||||
|
||||
impl From<ParseIntError> for ParseError<'_> {
|
||||
fn from(value: ParseIntError) -> Self {
|
||||
Self::ParseIntError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ParseError<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::LineMalformed(v) => write!(f, "Line is malformed: {v}"),
|
||||
Self::ParseIntError(e) => write!(f, "Unable to parse into integer: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct Voxel {
|
||||
x: i8,
|
||||
y: i8,
|
||||
z: i8,
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a str> for Voxel {
|
||||
type Error = ParseError<'a>;
|
||||
|
||||
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
|
||||
let components = value.split(',').collect::<Vec<&str>>();
|
||||
if components.len() != 3 {
|
||||
Err(Self::Error::LineMalformed(value))
|
||||
} else {
|
||||
Ok(Self {
|
||||
x: components[0].parse()?,
|
||||
y: components[1].parse()?,
|
||||
z: components[2].parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Voxel {
|
||||
fn neighbours(&self) -> [Self; 6] {
|
||||
[
|
||||
Self { x: self.x-1, y: self.y, z: self.z },
|
||||
Self { x: self.x+1, y: self.y, z: self.z },
|
||||
Self { x: self.x, y: self.y-1, z: self.z },
|
||||
Self { x: self.x, y: self.y+1, z: self.z },
|
||||
Self { x: self.x, y: self.y, z: self.z-1 },
|
||||
Self { x: self.x, y: self.y, z: self.z+1 },
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn find_total_surface_area(voxels: &BTreeSet<Voxel>) -> usize {
|
||||
voxels.iter()
|
||||
.map(|v| 6 - v.neighbours().iter().filter(|n| voxels.contains(n)).count())
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn find_area_reachable_from_origin(voxels: &BTreeSet<Voxel>) -> usize {
|
||||
let max_x = voxels.last().unwrap().x + 1;
|
||||
let max_y = voxels.iter().map(|v| v.y).max().unwrap() + 1;
|
||||
let max_z = voxels.iter().map(|v| v.z).max().unwrap() + 1;
|
||||
|
||||
let mut water = BTreeSet::from([Voxel { x: 0, y: 0, z: 0 }]);
|
||||
let mut water_last_step = water.clone();
|
||||
loop {
|
||||
let mut water_this_step = BTreeSet::new();
|
||||
for droplet in &water_last_step {
|
||||
for neighbour in droplet.neighbours() {
|
||||
if !water.contains(&neighbour) && !voxels.contains(&neighbour) && (-1..=max_x).contains(&neighbour.x) && (-1..=max_y).contains(&neighbour.y) && (-1..=max_z).contains(&neighbour.z) {
|
||||
water_this_step.insert(neighbour);
|
||||
water.insert(neighbour);
|
||||
}
|
||||
}
|
||||
}
|
||||
if water_this_step.is_empty() {
|
||||
break;
|
||||
}
|
||||
std::mem::swap(&mut water_this_step, &mut water_last_step);
|
||||
}
|
||||
|
||||
voxels.iter()
|
||||
.map(|v| v.neighbours().iter().filter(|n| water.contains(n)).count())
|
||||
.sum()
|
||||
}
|
||||
|
||||
pub fn run(input: &str) -> Result<(usize, usize), ParseError> {
|
||||
let voxels: BTreeSet<_> = input.lines().map(Voxel::try_from).collect::<Result<BTreeSet<_>, _>>()?;
|
||||
let first = find_total_surface_area(&voxels);
|
||||
let second = find_area_reachable_from_origin(&voxels);
|
||||
Ok((first, second))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
fn read_file(name: &str) -> String {
|
||||
read_to_string(name).expect(&format!("Unable to read file: {name}")[..]).trim().to_string()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sample() {
|
||||
let sample_input = read_file("tests/sample_input");
|
||||
assert_eq!(run(&sample_input), Ok((64, 58)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_challenge() {
|
||||
let challenge_input = read_file("tests/challenge_input");
|
||||
assert_eq!(run(&challenge_input), Ok((4320, 2456)));
|
||||
}
|
||||
}
|
|
@ -1,139 +0,0 @@
|
|||
use std::fs;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
struct Voxel {
|
||||
x: i8,
|
||||
y: i8,
|
||||
z: i8,
|
||||
}
|
||||
|
||||
impl Voxel {
|
||||
fn from(line: &str) -> Self {
|
||||
let components = line.split(',').collect::<Vec<&str>>();
|
||||
if components.len() != 3 {
|
||||
panic!("Unable to parse line {} into a Voxel.", line);
|
||||
}
|
||||
Self {
|
||||
x: components[0].parse().unwrap(),
|
||||
y: components[1].parse().unwrap(),
|
||||
z: components[2].parse().unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_total_surface_area(voxels: &Vec<Voxel>) -> usize {
|
||||
let mut total_surface = 0;
|
||||
for voxel in voxels {
|
||||
total_surface += 6 - voxels.iter()
|
||||
.filter(|v|
|
||||
v.x == voxel.x && v.y == voxel.y && (v.z - voxel.z).abs() == 1 ||
|
||||
v.x == voxel.x && (v.y - voxel.y).abs() == 1 && v.z == voxel.z ||
|
||||
(v.x - voxel.x).abs() == 1 && v.y == voxel.y && v.z == voxel.z
|
||||
)
|
||||
.count()
|
||||
|
||||
}
|
||||
total_surface
|
||||
}
|
||||
|
||||
fn find_area_reachable_from_origin(voxels: &Vec<Voxel>) -> usize {
|
||||
let max_x = voxels.iter().map(|v| v.x).max().unwrap() + 1;
|
||||
let max_y = voxels.iter().map(|v| v.y).max().unwrap() + 1;
|
||||
let max_z = voxels.iter().map(|v| v.z).max().unwrap() + 1;
|
||||
|
||||
let mut water = vec![vec![Voxel { x: 0, y: 0, z: 0 }]];
|
||||
loop {
|
||||
let mut water_this_step = Vec::new();
|
||||
for water_from_last_step in &water[water.len()-1] {
|
||||
let existing_water = water.iter().flatten().cloned().collect::<Vec<Voxel>>();
|
||||
for x_shift in [-1,1] {
|
||||
let this_dropplet = Voxel {
|
||||
x: water_from_last_step.x + x_shift,
|
||||
y: water_from_last_step.y,
|
||||
z: water_from_last_step.z,
|
||||
};
|
||||
if !existing_water.contains(&this_dropplet) &&
|
||||
!water_this_step.contains(&this_dropplet) &&
|
||||
!voxels.contains(&this_dropplet) &&
|
||||
this_dropplet.x <= max_x && this_dropplet.x >= -1 {
|
||||
water_this_step.push(this_dropplet);
|
||||
}
|
||||
}
|
||||
for y_shift in [-1,1] {
|
||||
let this_dropplet = Voxel {
|
||||
x: water_from_last_step.x,
|
||||
y: water_from_last_step.y + y_shift,
|
||||
z: water_from_last_step.z,
|
||||
};
|
||||
if !existing_water.contains(&this_dropplet) &&
|
||||
!water_this_step.contains(&this_dropplet) &&
|
||||
!voxels.contains(&this_dropplet) &&
|
||||
this_dropplet.y <= max_y && this_dropplet.y >= -1 {
|
||||
water_this_step.push(this_dropplet);
|
||||
}
|
||||
}
|
||||
for z_shift in [-1,1] {
|
||||
let this_dropplet = Voxel {
|
||||
x: water_from_last_step.x,
|
||||
y: water_from_last_step.y,
|
||||
z: water_from_last_step.z + z_shift,
|
||||
};
|
||||
if !existing_water.contains(&this_dropplet) &&
|
||||
!water_this_step.contains(&this_dropplet) &&
|
||||
!voxels.contains(&this_dropplet) &&
|
||||
this_dropplet.z <= max_z && this_dropplet.z >= -1 {
|
||||
water_this_step.push(this_dropplet);
|
||||
}
|
||||
}
|
||||
}
|
||||
if water_this_step.is_empty() {
|
||||
break;
|
||||
}
|
||||
water.push(water_this_step);
|
||||
}
|
||||
|
||||
let mut total_surface = 0;
|
||||
for voxel in voxels {
|
||||
total_surface += water.iter()
|
||||
.flatten()
|
||||
.filter(|v|
|
||||
v.x == voxel.x && v.y == voxel.y && (v.z - voxel.z).abs() == 1 ||
|
||||
v.x == voxel.x && (v.y - voxel.y).abs() == 1 && v.z == voxel.z ||
|
||||
(v.x - voxel.x).abs() == 1 && v.y == voxel.y && v.z == voxel.z
|
||||
)
|
||||
.count()
|
||||
|
||||
}
|
||||
total_surface
|
||||
}
|
||||
|
||||
fn read_file(path: &str) -> String {
|
||||
fs::read_to_string(path)
|
||||
.expect("File not found")
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let scan = read_file("input");
|
||||
let voxels = scan.lines().map(Voxel::from).collect::<Vec<_>>();
|
||||
|
||||
println!("The total surface Area including air pockets is {}.", find_total_surface_area(&voxels));
|
||||
println!("The outside surface Area is {}.", find_area_reachable_from_origin(&voxels));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sample_input() {
|
||||
let scan = read_file("tests/sample_input");
|
||||
let voxels = scan.lines().map(Voxel::from).collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(find_total_surface_area(&voxels), 64);
|
||||
assert_eq!(find_area_reachable_from_origin(&voxels), 58);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn challenge_input() {
|
||||
let scan = read_file("tests/input");
|
||||
let voxels = scan.lines().map(Voxel::from).collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(find_total_surface_area(&voxels), 4320);
|
||||
assert_eq!(find_area_reachable_from_origin(&voxels), 2456);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue