//! Day 12 use std::collections::hash_map::DefaultHasher; use std::collections::{hash_map::Entry, HashMap}; use std::hash::{Hash, Hasher}; use std::sync::Mutex; use itertools::Itertools; pub const DAY: u8 = 12; pub const TITLE: &str = "Hot Springs"; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] enum SpringState { Ok, Broken, Unknown, } impl SpringState { fn from_c(c: char) -> Self { match c { '.' => Self::Ok, '#' => Self::Broken, '?' => Self::Unknown, _ => panic!("invalid char `{c}`"), } } } #[derive(Debug)] struct Row { springs: Vec, groups: Vec, } impl Row { fn parse(input: &str) -> Self { let (p1, p2) = input.split_once(' ').unwrap(); Self { springs: p1.chars().map(SpringState::from_c).collect(), groups: p2.split(',').map(|n| n.parse().unwrap()).collect(), } } fn combinations(&self) -> usize { let mut cache = CcCache::new(); count_combinations_cached( &self.springs, &self.groups, None, 0, 0, 0, String::new(), &mut cache, ) .unwrap() } fn unfold(&self) -> Self { let mut springs = Vec::new(); for i in 0..5 { springs.extend(&self.springs); if i != 4 { springs.push(SpringState::Unknown); } } Self { springs, groups: self .groups .iter() .copied() .cycle() .take(self.groups.len() * 5) .collect(), } } } fn parse(input: Vec) -> Vec { input.iter().map(|r| Row::parse(r)).collect() } #[derive(Clone, Hash, PartialEq, Eq)] struct CcInput { pre: Option, si: usize, gi: usize, glen: usize, } type CcCache<'a> = HashMap>; fn count_combinations_cached( springs: &[SpringState], groups: &[usize], pre: Option, si: usize, gi: usize, glen: usize, tmp: String, cache: &mut CcCache, ) -> Option { let inp = CcInput { pre, si, gi, glen }; match cache.entry(inp) { Entry::Occupied(e) => *e.get(), Entry::Vacant(e) => { let res = count_combinations(springs, groups, pre, si, gi, glen, tmp); e.insert(res); res } } } fn count_combinations( springs: &[SpringState], groups: &[usize], pre: Option, si: usize, gi: usize, glen: usize, tmp: String, ) -> Option { if let Some(s) = pre.or(springs.get(si).copied()) { match s { SpringState::Ok => { if glen > 0 { // broken springs before if &glen == groups.get(gi)? { count_combinations(springs, groups, None, si + 1, gi + 1, 0, tmp + ".") } else { None } } else { count_combinations(springs, groups, None, si + 1, gi, 0, tmp + ".") } } SpringState::Broken => { let nc = glen + 1; if &nc > groups.get(gi)? { // more broken springs than specified None } else { count_combinations(springs, groups, None, si + 1, gi, nc, tmp + "#") } } SpringState::Unknown => { let mut s = 0; // Attempt 2 configurations s += count_combinations( springs, groups, Some(SpringState::Ok), si, gi, glen, tmp.clone(), ) .unwrap_or_default(); s += count_combinations( springs, groups, Some(SpringState::Broken), si, gi, glen, tmp.clone(), ) .unwrap_or_default(); // return amount of possible combinations Some(s).filter(|s| s > &0) } } } else if gi >= groups.len() || (gi == groups.len() - 1 && groups.last().unwrap() == &glen) { // println!("{tmp}; {groups:?}"); Some(1) } else { None } } pub fn part1(input: Vec) -> String { let rows = parse(input); rows.iter() // .map(Row::combinations) .map(|r| { let x = r.combinations(); // dbg!(x); x }) .sum::() .to_string() } pub fn part2(input: Vec) -> String { let rows = parse(input); rows.iter() // .map(Row::combinations) .map(|r| { let x = r.unfold().combinations(); dbg!(x); x }) .sum::() .to_string() } #[cfg(test)] mod tests { use super::*; #[test] fn tmp() { // .#.###.#.###### let r = Row::parse(".#? 1"); dbg!(r.combinations()); // dbg!(r.unfold().combinations()); } #[test] fn t_example1() { assert_eq!(part1(crate::read_example(DAY, 1)), "21"); } #[test] fn t_part1() { // < 11405 assert_eq!(part1(crate::read_input(DAY)), "8193"); } #[test] fn t_example2() { assert_eq!(part2(crate::read_example(DAY, 2)), "525152"); } #[test] fn t_part2() { assert_eq!(part2(crate::read_input(DAY)), "45322533163795"); } }