From 0a95d2fda3dff46483dbda923636f0775c6985e1 Mon Sep 17 00:00:00 2001 From: Dylan Thies Date: Tue, 26 Dec 2023 23:22:14 -0500 Subject: [PATCH] day 22 done part 2 takes too long but works --- 2023/Cargo.lock | 9 +++ 2023/day-22/Cargo.toml | 13 ++++ 2023/day-22/src/lib.rs | 4 + 2023/day-22/src/main.rs | 12 +++ 2023/day-22/src/part1.rs | 156 +++++++++++++++++++++++++++++++++++++++ 2023/day-22/src/part2.rs | 151 +++++++++++++++++++++++++++++++++++++ 6 files changed, 345 insertions(+) create mode 100644 2023/day-22/Cargo.toml create mode 100644 2023/day-22/src/lib.rs create mode 100644 2023/day-22/src/main.rs create mode 100644 2023/day-22/src/part1.rs create mode 100644 2023/day-22/src/part2.rs diff --git a/2023/Cargo.lock b/2023/Cargo.lock index 7c36fab..4760b1c 100644 --- a/2023/Cargo.lock +++ b/2023/Cargo.lock @@ -221,6 +221,15 @@ dependencies = [ "rstest", ] +[[package]] +name = "day-22" +version = "2023.0.0" +dependencies = [ + "glam", + "itertools", + "nom", +] + [[package]] name = "day-3" version = "2023.0.0" diff --git a/2023/day-22/Cargo.toml b/2023/day-22/Cargo.toml new file mode 100644 index 0000000..c49ea3e --- /dev/null +++ b/2023/day-22/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "day-22" +version.workspace = true +edition.workspace = true +authors.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nom = { workspace = true } +itertools = {workspace = true } +glam.workspace = true diff --git a/2023/day-22/src/lib.rs b/2023/day-22/src/lib.rs new file mode 100644 index 0000000..3fafe8d --- /dev/null +++ b/2023/day-22/src/lib.rs @@ -0,0 +1,4 @@ +pub mod part1; +pub use crate::part1::*; +pub mod part2; +pub use crate::part2::*; diff --git a/2023/day-22/src/main.rs b/2023/day-22/src/main.rs new file mode 100644 index 0000000..db387a2 --- /dev/null +++ b/2023/day-22/src/main.rs @@ -0,0 +1,12 @@ +#![warn(clippy::all, clippy::pedantic)] + +use day_22::part1; +use day_22::part2; + +fn main() { + let input = include_str!("./input.txt"); + let part1_result = part1(input); + println!("part 1: {part1_result}"); + let part2_result = part2(input); + println!("part 2: {part2_result}"); +} diff --git a/2023/day-22/src/part1.rs b/2023/day-22/src/part1.rs new file mode 100644 index 0000000..2814213 --- /dev/null +++ b/2023/day-22/src/part1.rs @@ -0,0 +1,156 @@ +#![warn(clippy::all, clippy::pedantic)] + +use std::collections::HashMap; + +use glam::{UVec3, Vec3Swizzles}; +use itertools::Itertools; +use nom::{ + bytes::complete::tag, character::complete, multi::separated_list1, sequence::separated_pair, + IResult, Parser, +}; + +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +struct Brick { + pub cubes: Vec, +} + +impl Brick { + fn supports(&self, other: &Self) -> bool { + let max_cubes = self.cubes.iter().max_set_by_key(|cube| cube.z); + let min_cubes = other.cubes.iter().min_set_by_key(|cube| cube.z); + let top = max_cubes[0].z; + let bottom = min_cubes[0].z; + if top + 1 != bottom { + return false; + } + max_cubes.iter().any(|cube| { + min_cubes + .iter() + .map(|t_cube| t_cube.xy()) + .contains(&cube.xy()) + }) + } + + #[allow(dead_code)] + fn supported_by(&self, other: &Self) -> bool { + other.supports(self) + } +} + +/// day 22 part 1 of aoc 2023 +/// +/// # Arguments +/// - input the input for today's puzzle +/// +/// # Panics +/// panics whne it cannot parse the input OR when ever the number of game numbers is greater than +#[must_use] +pub fn part1(input: &str) -> String { + let (_, mut bricks) = parse_input(input).expect("AOC should have valid input"); + bricks.sort_by(|a, b| { + a.cubes + .iter() + .map(|cube| cube.z) + .min() + .unwrap() + .cmp(&b.cubes.iter().map(|cube| cube.z).min().unwrap()) + }); + //lower the bricks + let stacked = bricks.iter().fold(Vec::new(), |mut acc, brick| { + let bottom_cubes = brick.cubes.iter().min_set_by_key(|cube| cube.z); + let bottom_z = bottom_cubes.iter().map(|cube| cube.z).min().unwrap(); + let top_underneath_cubes = acc + .iter() + .flat_map(|settle_brick: &Brick| settle_brick.cubes.iter()) + .filter_map(|cube| { + bottom_cubes + .iter() + .map(|c| c.xy()) + .contains(&cube.xy()) + .then_some(cube.z) + }) + .max() + .unwrap_or(0); + let step_down = bottom_z - top_underneath_cubes - 1; + let new_cubes = brick + .cubes + .iter() + .map(|cube| *cube - (UVec3::Z * step_down)) + .collect(); + acc.push(Brick { cubes: new_cubes }); + + acc + }); + let stack_len = stacked.len(); + + //do the check + let undisolvable = stacked + .into_iter() + .permutations(2) + //.inspect(|a| println!("{a:?}")) + .filter_map(|a| { + let [ref a_brick, ref b_brick] = a[..] else { + unimplemented!("should neer reach here") + }; + a_brick + .supports(b_brick) + .then_some((a_brick.clone(), b_brick.clone())) + }) + .fold(HashMap::new(), |mut acc, (a_brick, b_brick)| { + acc.entry(b_brick) + .and_modify(|v: &mut Vec| v.push(a_brick.clone())) + .or_insert(vec![a_brick.clone()]); + acc + }) + .values() + .filter_map(|v| (v.len() == 1).then_some(v[0].clone())) + .unique() + //.inspect(|a| println!("{a:?}")) + .count(); + (stack_len - undisolvable).to_string() +} + +fn parse_corner(input: &str) -> IResult<&str, UVec3> { + let (input, x) = complete::u32(input)?; + let (input, _) = tag(",")(input)?; + let (input, y) = complete::u32(input)?; + let (input, _) = tag(",")(input)?; + let (input, z) = complete::u32(input)?; + Ok((input, UVec3::new(x, y, z))) +} + +fn parse_input(input: &str) -> IResult<&str, Vec> { + separated_list1( + complete::line_ending, + separated_pair(parse_corner, tag("~"), parse_corner).map(|(a, b)| { + let mut cubes = Vec::new(); + for x in (a.x.min(b.x))..=(a.x.max(b.x)) { + for y in (a.y.min(b.y))..=(a.y.max(b.y)) { + for z in (a.z.min(b.z))..=(a.z.max(b.z)) { + cubes.push(UVec3::new(x, y, z)); + } + } + } + Brick { cubes } + }), + )(input) +} + +#[cfg(test)] +mod test { + use super::*; + + const INPUT: &str = "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"; + + #[test] + fn part1_works() { + let result = part1(INPUT); + assert_eq!(result, "5".to_string()); + } +} diff --git a/2023/day-22/src/part2.rs b/2023/day-22/src/part2.rs new file mode 100644 index 0000000..e669d7e --- /dev/null +++ b/2023/day-22/src/part2.rs @@ -0,0 +1,151 @@ +#![warn(clippy::all, clippy::pedantic)] + +use glam::{UVec3, Vec3Swizzles}; +use itertools::Itertools; +use nom::{ + bytes::complete::tag, character::complete, multi::separated_list1, sequence::separated_pair, + IResult, Parser, +}; + +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +struct Brick { + pub cubes: Vec, +} + +impl Brick { + fn supports(&self, other: &Self) -> bool { + let max_cubes = self.cubes.iter().max_set_by_key(|cube| cube.z); + let min_cubes = other.cubes.iter().min_set_by_key(|cube| cube.z); + let top = max_cubes[0].z; + let bottom = min_cubes[0].z; + if top + 1 != bottom { + return false; + } + max_cubes.iter().any(|cube| { + min_cubes + .iter() + .map(|t_cube| t_cube.xy()) + .contains(&cube.xy()) + }) + } + + #[allow(dead_code)] + fn supported_by(&self, other: &Self) -> bool { + other.supports(self) + } +} + +fn stacks_up(stack: &[Brick]) -> (Vec, usize) { + stack + .iter() + .fold((Vec::new(), 0_usize), |(mut acc, mut dropped), brick| { + let bottom_cubes = brick.cubes.iter().min_set_by_key(|cube| cube.z); + let bottom_z = bottom_cubes.iter().map(|cube| cube.z).min().unwrap(); + let top_underneath_cubes = acc + .iter() + .flat_map(|settle_brick: &Brick| settle_brick.cubes.iter()) + .filter_map(|cube| { + bottom_cubes + .iter() + .map(|c| c.xy()) + .contains(&cube.xy()) + .then_some(cube.z) + }) + .max() + .unwrap_or(0); + let step_down = bottom_z - top_underneath_cubes - 1; + if step_down != 0 { + dropped += 1; + } + let new_cubes = brick + .cubes + .iter() + .map(|cube| *cube - (UVec3::Z * step_down)) + .collect(); + acc.push(Brick { cubes: new_cubes }); + + (acc, dropped) + }) +} + +/// day 22 part 2 of aoc 2023 +/// +/// # Arguments +/// - input the input for today's puzzle +/// +/// # Panics +/// panics whne it cannot parse the input OR when ever the number of game numbers is greater than +#[must_use] +pub fn part2(input: &str) -> String { + let (_, mut bricks) = parse_input(input).expect("AOC should have valid input"); + bricks.sort_by(|a, b| { + a.cubes + .iter() + .map(|cube| cube.z) + .min() + .unwrap() + .cmp(&b.cubes.iter().map(|cube| cube.z).min().unwrap()) + }); + //lower the bricks + let (stacked, _) = stacks_up(&bricks); + + //do the check + stacked + .iter() + .map(|brick| { + let to_drop = &stacked + .iter() + .filter(|b| *b != brick) + .cloned() + .collect::>(); + let (_, fell) = stacks_up(to_drop); + fell + }) + .sum::() + .to_string() +} + +fn parse_corner(input: &str) -> IResult<&str, UVec3> { + let (input, x) = complete::u32(input)?; + let (input, _) = tag(",")(input)?; + let (input, y) = complete::u32(input)?; + let (input, _) = tag(",")(input)?; + let (input, z) = complete::u32(input)?; + Ok((input, UVec3::new(x, y, z))) +} + +fn parse_input(input: &str) -> IResult<&str, Vec> { + separated_list1( + complete::line_ending, + separated_pair(parse_corner, tag("~"), parse_corner).map(|(a, b)| { + let mut cubes = Vec::new(); + for x in (a.x.min(b.x))..=(a.x.max(b.x)) { + for y in (a.y.min(b.y))..=(a.y.max(b.y)) { + for z in (a.z.min(b.z))..=(a.z.max(b.z)) { + cubes.push(UVec3::new(x, y, z)); + } + } + } + Brick { cubes } + }), + )(input) +} + +#[cfg(test)] +mod test { + use super::*; + + const INPUT: &str = "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"; + + #[test] + fn part2_works() { + let result = part2(INPUT); + assert_eq!(result, "7".to_string()); + } +}