summaryrefslogtreecommitdiff
path: root/2023/src/bin/day_22.rs
diff options
context:
space:
mode:
Diffstat (limited to '2023/src/bin/day_22.rs')
-rw-r--r--2023/src/bin/day_22.rs161
1 files changed, 161 insertions, 0 deletions
diff --git a/2023/src/bin/day_22.rs b/2023/src/bin/day_22.rs
new file mode 100644
index 0000000..750f975
--- /dev/null
+++ b/2023/src/bin/day_22.rs
@@ -0,0 +1,161 @@
+use nalgebra::Point3;
+use nom::{
+ bytes::complete::tag,
+ character::complete::{line_ending, u32},
+ combinator::map,
+ multi::separated_list1,
+ sequence::{separated_pair, tuple},
+ IResult,
+};
+use std::fs;
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+ let input = fs::read_to_string("inputs/day_22.txt")?;
+ let pile = BrickPile::parser(&input).unwrap().1;
+ let settled_pile = pile.settle();
+ let brickfall_sum = settled_pile.brickfall_sum();
+ dbg!(&brickfall_sum.count_disintegratable_blocks());
+ dbg!(&brickfall_sum.sum_brickfalls());
+
+ Ok(())
+}
+
+#[derive(Debug)]
+struct BrickPile(Vec<Brick>);
+
+#[derive(Debug)]
+struct SettledBrickPile {
+ bricks: Vec<Brick>,
+ settled_count: usize,
+}
+
+#[derive(Debug, Clone)]
+struct Brick {
+ bottom: Point3<u32>, // the lowest z will always be here. The top might still have the same z.
+ top: Point3<u32>,
+}
+
+#[derive(Debug)]
+struct BrickfallSum(Vec<usize>);
+
+impl BrickPile {
+ fn parser(input: &str) -> IResult<&str, Self> {
+ map(separated_list1(line_ending, Brick::parser), BrickPile)(input)
+ }
+
+ fn settle(&self) -> SettledBrickPile {
+ let mut settled_bricks = self.0.clone();
+ let mut has_fallen = vec![false; settled_bricks.len()];
+
+ let mut all_settled = false;
+
+ while !all_settled {
+ all_settled = true;
+ for self_i in 0..settled_bricks.len() {
+ let this_brick_is_resting_on_something = settled_bricks[self_i]
+ .is_resting_on_ground()
+ || (0..settled_bricks.len()).any(|other_i| {
+ self_i != other_i
+ && settled_bricks[self_i].is_resting_on_other(&settled_bricks[other_i])
+ });
+
+ if !this_brick_is_resting_on_something {
+ settled_bricks[self_i].fall_one();
+ has_fallen[self_i] = true;
+ all_settled = false;
+ }
+ }
+ }
+
+ SettledBrickPile {
+ bricks: settled_bricks,
+ settled_count: has_fallen.iter().filter(|f| **f).count(),
+ }
+ }
+}
+
+impl Brick {
+ fn parser(input: &str) -> IResult<&str, Self> {
+ map(
+ separated_pair(point_parser, tag("~"), point_parser),
+ |(a, b)| {
+ if a.z < b.z {
+ Brick { bottom: a, top: b }
+ } else {
+ Brick { top: a, bottom: b }
+ }
+ },
+ )(input)
+ }
+
+ fn min_x(&self) -> u32 {
+ self.bottom.x.min(self.top.x)
+ }
+
+ fn max_x(&self) -> u32 {
+ self.bottom.x.max(self.top.x)
+ }
+
+ fn min_y(&self) -> u32 {
+ self.bottom.y.min(self.top.y)
+ }
+
+ fn max_y(&self) -> u32 {
+ self.bottom.y.max(self.top.y)
+ }
+
+ fn is_resting_on_ground(&self) -> bool {
+ self.bottom.z == 1
+ }
+
+ fn is_resting_on_other(&self, other: &Self) -> bool {
+ self.bottom.z == other.top.z + 1
+ && self.min_x() <= other.max_x()
+ && other.min_x() <= self.max_x()
+ && self.min_y() <= other.max_y()
+ && other.min_y() <= self.max_y()
+ }
+
+ fn fall_one(&mut self) {
+ self.bottom.z -= 1;
+ self.top.z -= 1;
+ }
+}
+
+fn point_parser(input: &str) -> IResult<&str, Point3<u32>> {
+ map(
+ tuple((u32, tag(","), u32, tag(","), u32)),
+ |(x, _, y, _, z)| Point3::new(x, y, z),
+ )(input)
+}
+
+impl SettledBrickPile {
+ fn brickfall_sum(&self) -> BrickfallSum {
+ BrickfallSum(
+ (0..self.bricks.len())
+ .map(|self_i| {
+ self.count_bricks_that_would_fall_if_this_one_is_disintegrated(self_i)
+ })
+ .collect(),
+ )
+ }
+
+ fn count_bricks_that_would_fall_if_this_one_is_disintegrated(&self, i: usize) -> usize {
+ let mut unsettled_bricks = self.bricks.clone();
+ unsettled_bricks.remove(i);
+ let unsettled_bricks = BrickPile(unsettled_bricks);
+
+ let resettled = unsettled_bricks.settle();
+ resettled.settled_count
+ }
+}
+
+impl BrickfallSum {
+ fn count_disintegratable_blocks(&self) -> usize {
+ self.0.iter().filter(|fallen| **fallen == 0).count()
+ }
+
+ fn sum_brickfalls(&self) -> usize {
+ self.0.iter().sum()
+ }
+}