use crate::{Problem, Solution}; pub struct Day12; impl Problem for Day12 { const DAY: u8 = 12; const INPUT: &'static str = include_str!("../input/day_12.txt"); } impl Solution for Day12 { type Answer1 = usize; type Answer2 = usize; fn part_1(input: &str) -> anyhow::Result { Ok(input .lines() .map(std::str::FromStr::from_str) .try_collect::>>()? .into_iter() .map(Row::solve) .sum()) } fn part_2(input: &str) -> anyhow::Result { Ok(input .lines() .map(std::str::FromStr::from_str) .try_collect::>>()? .into_iter() .map(Row::solve) .sum()) } } #[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] struct Row { states: Vec, groups: Vec, } impl Row { fn new(states: Vec, groups: Vec) -> Self { let states = std::iter::repeat(states) .take(REP as usize) .intersperse(vec![State::Unknown]) .flatten() .collect::>(); let groups = std::iter::repeat(groups) .take(REP as usize) .flatten() .collect::>(); Self { states, groups } } fn solve(mut self) -> usize { self.states.push(State::Good); let mut groups = vec![Group::default()]; for state in self.states.into_iter() { let mut next_groups = Vec::new(); for Group { seq, index, count, prev, } in groups { if state == State::Unknown || state == State::Bad { next_groups.push(Group { seq: seq + 1, index, count, prev: Some(State::Bad), }) } if state == State::Unknown || state == State::Good { if prev != Some(State::Bad) { next_groups.push(Group { seq: 0, index, count, prev: Some(State::Good), }); } else if self.groups.get(index).is_some_and(|s| *s == seq) { next_groups.push(Group { seq: 0, index: index + 1, count, prev: Some(State::Good), }); } } } next_groups.sort(); groups = next_groups.into_iter().fold(Vec::new(), |mut acc, group| { if let Some(last) = acc.last_mut() { if last.seq == group.seq && last.index == group.index && last.prev == group.prev { last.count += group.count; return acc; } } acc.push(group); acc }); } groups .into_iter() .filter_map(|g| (g.index == self.groups.len()).then_some(g.count)) .sum() } } impl std::str::FromStr for Row { type Err = anyhow::Error; fn from_str(s: &str) -> Result { let (states, groups) = s.split_once(' ').unwrap(); let states = states .chars() .map(TryFrom::try_from) .try_collect::>()?; let groups = groups .split(',') .map(std::str::FromStr::from_str) .try_collect::>()?; Ok(Self::new(states, groups)) } } impl std::fmt::Display for Row { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.states .iter() .try_for_each(|s| write!(f, "{}", char::from(*s)))?; self.groups.iter().enumerate().try_for_each(|(i, n)| { if i == 0 { write!(f, " {n}") } else { write!(f, ",{n}") } }) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] struct Group { seq: usize, index: usize, count: usize, prev: Option, } impl Default for Group { fn default() -> Self { Self { seq: 0, index: 0, count: 1, prev: None, } } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] enum State { Good, Bad, Unknown, } impl TryFrom for State { type Error = anyhow::Error; fn try_from(value: char) -> Result { match value { '.' => Ok(State::Good), '#' => Ok(State::Bad), '?' => Ok(State::Unknown), c => Err(anyhow::format_err!("Unknown spring condition: {c}")), } } } impl From for char { fn from(value: State) -> Self { match value { State::Good => '.', State::Bad => '#', State::Unknown => '?', } } } #[cfg(test)] mod tests { use super::*; const INPUT: &str = indoc::indoc! {" ???.### 1,1,3 .??..??...?##. 1,1,3 ?#?#?#?#?#?#?#? 1,3,1,6 ????.#...#... 4,1,1 ????.######..#####. 1,6,5 ?###???????? 3,2,1 "}; #[test] fn test_part_1() -> anyhow::Result<()> { Ok(assert_eq!(21, Day12::part_1(INPUT)?)) } #[test] fn test_part_2() -> anyhow::Result<()> { Ok(assert_eq!(525152, Day12::part_2(INPUT)?)) } }