diff --git a/data/examples/22.txt b/data/examples/22.txt new file mode 100644 index 000000000..be1ffa5 --- /dev/null +++ b/data/examples/22.txt @@ -0,0 +1,7 @@ +1,0,1~1,2,1 +0,0,2~2,0,2 +0,2,3~2,2,3 +0,0,4~0,2,4 +2,0,5~2,2,5 +0,1,6~2,1,6 +1,1,9~1,1,8 diff --git a/src/bin/22.rs b/src/bin/22.rs new file mode 100644 index 000000000..8f3f01a --- /dev/null +++ b/src/bin/22.rs @@ -0,0 +1,143 @@ +use std::{collections::HashMap, str::FromStr}; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +struct Brick { + from: (usize, usize, usize), + to: (usize, usize, usize), +} + +#[derive(Debug)] +struct ParseBrickError; + +impl FromStr for Brick { + type Err = ParseBrickError; + fn from_str(s: &str) -> Result { + let (first, second) = s.split_once('~').ok_or(ParseBrickError)?; + + let sorted: Vec<_> = first + .split(',') + .filter_map(|y| y.parse().ok()) + .zip(second.split(',').filter_map(|y| y.parse().ok())) + .map(|(x, y)| if x < y { (x, y) } else { (y, x) }) + .collect(); + let from = (sorted[0].0, sorted[1].0, sorted[2].0); + let to = (sorted[0].1, sorted[1].1, sorted[2].1); + + Ok(Self { from, to }) + } +} + +impl Brick { + fn height(&self) -> usize { + self.to.2 - self.from.2 + 1 + } + + fn points(&self) -> impl Iterator + '_ { + (self.from.0..=self.to.0).flat_map(move |x| { + (self.from.1..=self.to.1) + .flat_map(move |y| (self.from.2..=self.to.2).map(move |z| (x, y, z))) + }) + } +} + +impl PartialOrd for Brick { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Brick { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.from + .2 + .cmp(&other.from.2) + .then((self.from.0, self.from.1).cmp(&(other.from.0, other.from.1))) + } +} + +#[derive(Debug, Clone)] +struct Tower { + bricks: Vec, +} + +impl Tower { + fn new(mut bricks: Vec) -> Self { + bricks.sort(); + let mut ret = Self { bricks }; + ret.compress(None); + ret + } + + fn compress(&mut self, skip: Option) -> usize { + let mut heights: HashMap<(usize, usize), usize> = HashMap::new(); + let mut moved = 0; + + for brick in self + .bricks + .iter_mut() + .enumerate() + .filter(|(i, _)| *i != skip.unwrap_or(usize::MAX)) + .map(|(_, x)| x) + { + let height = brick.height(); + let new_height = brick + .points() + .map(|(x, y, _)| *heights.entry((x, y)).or_default()) + .max() + .unwrap(); + + for (x, y, _) in brick.points() { + *heights.get_mut(&(x, y)).unwrap() = new_height + height; + } + + if new_height + 1 == brick.from.2 { + continue; + } + + if skip.is_none() { + brick.from.2 = new_height + 1; + brick.to.2 = new_height + height; + } + + moved += 1; + } + + moved + } +} + +pub fn part_one(input: &str) -> Option { + let mut tower = Tower::new(input.lines().filter_map(|x| x.parse().ok()).collect()); + + Some( + (0..tower.bricks.len()) + .map(|i| tower.compress(Some(i))) + .filter(|m| *m == 0) + .count(), + ) +} + +pub fn part_two(input: &str) -> Option { + let mut tower = Tower::new(input.lines().filter_map(|x| x.parse().ok()).collect()); + + Some( + (0..tower.bricks.len()) + .map(|i| tower.compress(Some(i))) + .sum(), + ) +} + +aoc::solution!(22); + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_part_one() { + assert_eq!(part_one(&aoc::template::read_file("examples", 22)), Some(5)); + } + #[test] + fn test_part_two() { + assert_eq!(part_two(&aoc::template::read_file("examples", 22)), Some(7)); + } +}