From 9d18dc79dae54cf51c0860ebdf8e5417378b18d4 Mon Sep 17 00:00:00 2001 From: Bert Peters Date: Sat, 23 Dec 2023 21:05:10 +0100 Subject: [PATCH] Implement 2023 day 22 part 1 --- 2023/src/day22.rs | 123 +++++++++++++++++++++++++++++++++++++++- 2023/src/samples/22.txt | 7 +++ 2 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 2023/src/samples/22.txt diff --git a/2023/src/day22.rs b/2023/src/day22.rs index 7c1760f..35a4a72 100644 --- a/2023/src/day22.rs +++ b/2023/src/day22.rs @@ -1,7 +1,126 @@ -pub fn part1(_input: &[u8]) -> anyhow::Result { - anyhow::bail!("Not implemented") +use std::collections::HashSet; + +use nom::bytes::complete::tag; +use nom::combinator::map; +use nom::multi::many1; +use nom::sequence::terminated; +use nom::sequence::tuple; +use nom::IResult; + +use crate::common::minmax; +use crate::common::parse_input; + +struct Brick([[u16; 3]; 2]); + +impl Brick { + fn lowest(&self) -> u16 { + Ord::min(self.0[0][2], self.0[1][2]) + } + + fn iter_squares(&self) -> impl Iterator { + let xs = minmax(self.0[0][0], self.0[1][0]); + let ys = minmax(self.0[0][1], self.0[1][1]); + + (ys.0..=ys.1).flat_map(move |z| (xs.0..=xs.1).map(move |x| (x, z))) + } + + fn parse(i: &[u8]) -> IResult<&[u8], Brick> { + use nom::character::complete::u16; + let parse_coordinates = |i| { + map( + tuple((terminated(u16, tag(",")), terminated(u16, tag(",")), u16)), + |(x, y, z)| [x, y, z], + )(i) + }; + + map( + tuple(( + terminated(parse_coordinates, tag("~")), + terminated(parse_coordinates, tag("\n")), + )), + |(first, second)| Brick([first, second]), + )(i) + } +} + +fn parse_bricks(i: &[u8]) -> IResult<&[u8], Vec> { + many1(Brick::parse)(i) +} + +pub fn part1(input: &[u8]) -> anyhow::Result { + let mut bricks = parse_input(input, parse_bricks)?; + bricks.sort_unstable_by_key(Brick::lowest); + + let mut width = 0; + let mut breadth = 0; + + for brick in &bricks { + width = Ord::max(width, Ord::max(brick.0[0][0], brick.0[1][0])); + breadth = Ord::max(breadth, Ord::max(brick.0[0][2], brick.0[1][2])); + } + + let width = width as usize + 1; + let breadth = breadth as usize + 1; + + let mut height_map = vec![0; width * breadth]; + let mut top_map = vec![usize::MAX; height_map.len()]; + let mut supported = vec![0u8; bricks.len()]; + let mut supporting = vec![Vec::new(); bricks.len()]; + + let mut temp = HashSet::new(); + + for ((i, brick), supported) in bricks.iter().enumerate().zip(&mut supported) { + let max_z = brick + .iter_squares() + .map(|(x, y)| height_map[x as usize + (y as usize) * width]) + .max() + .expect("Guaranteed non-empty iterator."); + + let zs = minmax(brick.0[0][2], brick.0[1][2]); + + debug_assert!( + zs.0 >= max_z, + "Falling piece should be higher than existing tower" + ); + + let new_y = max_z + zs.1 - zs.0 + 1; + + for (x, y) in brick.iter_squares() { + let offset = x as usize + (y as usize) * width; + if height_map[offset] == max_z && max_z > 0 { + debug_assert_ne!(top_map[offset], usize::MAX); + temp.insert(top_map[offset]); + } + height_map[offset] = new_y; + top_map[offset] = i; + } + + *supported = temp.len().try_into()?; + for support in temp.drain() { + supporting[support].push(i); + } + } + + let removable = supporting + .iter() + .filter(|b| b.iter().all(|&other| supported[other] >= 2)) + .count(); + + Ok(removable.to_string()) } pub fn part2(_input: &[u8]) -> anyhow::Result { anyhow::bail!("Not implemented") } + +#[cfg(test)] +mod tests { + use super::*; + + const SAMPLE: &[u8] = include_bytes!("samples/22.txt"); + + #[test] + fn sample_part1() { + assert_eq!("5", part1(SAMPLE).unwrap()); + } +} diff --git a/2023/src/samples/22.txt b/2023/src/samples/22.txt new file mode 100644 index 0000000..43a7fc5 --- /dev/null +++ b/2023/src/samples/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,8~1,1,9