#![warn(clippy::all, clippy::pedantic)] use std::collections::HashSet; use glam::UVec2; use nom::{ bytes::complete::is_a, character::complete, multi::separated_list1, IResult, sequence::tuple, }; struct Drawing { pub size: UVec2, pub mounds: HashSet, } impl Drawing { fn process(&self) -> u32 { let (max_col, max_row) = self.size.into(); let col_score = (1..max_col ) .filter_map(|reflect_col| { let span = reflect_col.min(max_col - reflect_col); (self.mounds .iter() .filter(|mound| mound.x + span >= reflect_col && mound.x < reflect_col) .map(|mound| (2* reflect_col - mound.x - 1, mound.y).into()) .all(|mound_reflect| self.mounds.get(&mound_reflect).is_some()) && self.mounds .iter() .filter(|mound|mound.x < reflect_col + span && mound.x >= reflect_col) .map(|mound| ( 2*reflect_col - mound.x - 1,mound.y).into()) .all(|mound_reflect| self.mounds.get(&mound_reflect).is_some()) ) .then_some(reflect_col) }) .sum::(); let row_score = (1..max_row ) .filter_map(|reflect_row| { let span = reflect_row.min(max_row - reflect_row); (self.mounds .iter() .filter(|mound| mound.y + span >= reflect_row && mound.y < reflect_row) .map(|mound| (mound.x, 2 * reflect_row - mound.y - 1).into()) .all(|mound_reflect| self.mounds.get(&mound_reflect).is_some()) && self.mounds .iter() .filter(|mound|mound.y < reflect_row + span && mound.y >= reflect_row) .map(|mound| (mound.x, 2*reflect_row - mound.y - 1).into()) .all(|mound_reflect| self.mounds.get(&mound_reflect).is_some()) ) .then_some(reflect_row) }) .sum::() * 100; col_score + row_score } } #[must_use] pub fn part1(_input: &str) -> String { let (_, drawings) = parse_input(_input).expect("aoc always valid"); drawings.iter().map(Drawing::process).sum::().to_string() } fn parse_drawing(input: &str) -> IResult<&str, Drawing> { let (input, rows) = separated_list1(complete::line_ending, is_a(".#"))(input)?; let max_rows = u32::try_from(rows.len()).expect("shouldn't be that big"); let max_cols = u32::try_from(rows[0].len()).expect("shouldn't be that big"); let size = UVec2::from((max_cols, max_rows)); let mounds = rows .iter() .enumerate() .flat_map(|y| { y.1.chars().enumerate().map(move |x| { ( u32::try_from(x.0).expect("should be 32"), u32::try_from(y.0).expect("should not fail"), x.1, ) }) }) .filter_map(|(x, y, mound)| (mound == '#').then_some(UVec2::from((x, y)) )) .collect(); Ok((input, Drawing { size, mounds })) } fn parse_input(input: &str) -> IResult<&str, Vec>{ separated_list1(tuple((complete::line_ending, complete::line_ending)), parse_drawing)(input) } #[cfg(test)] mod test { use super::*; use rstest::rstest; #[rstest] #[case( "#.##..##. ..#.##.#. ##......# ##......# ..#.##.#. ..##..##. #.#.##.#.", 5 )] #[case( "#...##..# #....#..# ..##..### #####.##. #####.##. ..##..### #....#..#", 400 )] fn board_test(#[case] input: &str, #[case] expected: u32) { let (_, drawing) = parse_drawing(input).expect("Parsing should work"); assert_eq!(drawing.process(), expected); } const INPUT: &str = "#.##..##. ..#.##.#. ##......# ##......# ..#.##.#. ..##..##. #.#.##.#. #...##..# #....#..# ..##..### #####.##. #####.##. ..##..### #....#..#"; #[test] fn part1_works() { let result = part1(INPUT); assert_eq!(result, "405".to_string()); } }