use std::collections::HashMap; pub fn run() { let input = include_str!("../input/day12.txt"); // let input = r#"???.### 1,1,3 // .??..??...?##. 1,1,3 // ?#?#?#?#?#?#?#? 1,3,1,6 // ????.#...#... 4,1,1 // ????.######..#####. 1,6,5 // ?###???????? 3,2,1"#; let mut lines = input .trim() .lines() .map(|l| parse_line(l)) .collect::>(); let count_sum = lines .iter() .map(|(cs, runs)| { count_arrangements( CountInputs { cs: &cs, assuming_first_char: None, in_run: false, runs: runs.clone(), }, &mut HashMap::new(), ) }) .sum::(); dbg!(count_sum); for l in lines.iter_mut() { unfold_line(&mut l.0, &mut l.1); } let count_sum2 = lines .iter() .map(|(cs, runs)| { let res = count_arrangements( CountInputs { cs: &cs, assuming_first_char: None, in_run: false, runs: runs.clone(), }, &mut HashMap::new(), ); println!("finished line"); res }) .sum::(); dbg!(count_sum2); } fn parse_line(s: &str) -> (Vec, Vec) { let (l, r) = s.trim().split_once(" ").unwrap(); ( l.chars().collect(), r.split(",").map(|s| s.parse().unwrap()).collect(), ) } fn unfold_line(cs: &mut Vec, runs: &mut Vec) { let orig_cs = cs.to_vec(); let orig_runs = runs.to_vec(); for _ in 0..4 { cs.push('?'); cs.extend(orig_cs.iter()); runs.extend(orig_runs.iter()); } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] struct CountInputs<'a> { cs: &'a [char], assuming_first_char: Option, in_run: bool, runs: Vec, } fn count_arrangements<'a: 'b, 'b>( inputs: CountInputs<'a>, // cs: &[char], // assuming_first_char: Option, // // mut accepted: Vec, // in_run: bool, // runs: &[u8], cache: &mut HashMap, usize>, ) -> usize { if let Some(result) = cache.get(&inputs) { return *result; } if inputs.runs.is_empty() { assert!(!inputs.in_run); for &c in inputs.cs { if c == '#' { // invalid, since there are remaining broken springs with no broken runs cache.insert(inputs, 0); return 0; } else { // accepted.push('.'); } } // valid // println!("{}", accepted.iter().collect::()); cache.insert(inputs, 1); return 1; } if inputs.cs.is_empty() { // if we're out of chars, valid iff there are no runs remaining if inputs.runs.is_empty() { assert!(!inputs.in_run); cache.insert(inputs, 1); return 1; } else { cache.insert(inputs, 0); return 0; } } let first = inputs.assuming_first_char.unwrap_or(inputs.cs[0]); if first == '#' { if inputs.runs[0] == 1 { // current run should be finished if inputs.cs.len() > 1 { if inputs.cs[1] == '#' { // if the next char is #, invalid b/c the current run would continue return 0; } else if inputs.cs[1] == '.' || inputs.cs[1] == '?' { // let mut accepted = accepted.to_vec(); // accepted.push('#'); // accepted.push('.'); // current run does indeed end return count_arrangements( CountInputs { cs: &inputs.cs[2..], assuming_first_char: None, in_run: false, runs: inputs.runs[1..].iter().copied().collect::>(), }, cache, ); } else { panic!("invalid char {}", inputs.cs[1]); } } else { // end of string, valid iff there are no more runs if inputs.runs.len() == 1 { // println!( // "{}#, last run: {}", // accepted.iter().collect::(), // runs[0] // ); cache.insert(inputs, 1); return 1; } else { cache.insert(inputs, 0); return 0; } } } else { // accepted.push('#'); // current run continues let mut runs_copy = inputs.runs.to_vec(); runs_copy[0] -= 1; // println!( // "{}, current run continuing, {:?}", // accepted.iter().collect::(), // runs_copy // ); return count_arrangements( CountInputs { cs: &inputs.cs[1..], assuming_first_char: None, in_run: true, runs: runs_copy, }, cache, ); } } else if first == '?' { // let mut accepted_dot = accepted.clone(); // accepted_dot.push('.'); let res = count_arrangements( CountInputs { cs: inputs.cs, assuming_first_char: Some('.'), in_run: inputs.in_run, runs: inputs.runs.clone(), }, cache, ) + count_arrangements( CountInputs { cs: inputs.cs, assuming_first_char: Some('#'), in_run: inputs.in_run, runs: inputs.runs.clone(), }, cache, ); cache.insert(inputs, res); return res; } else if first == '.' { if inputs.in_run { // the current run ended prematurely cache.insert(inputs, 0); return 0; } else { // accepted.push('.'); return count_arrangements( CountInputs { cs: &inputs.cs[1..], assuming_first_char: None, in_run: false, runs: inputs.runs, }, cache, ); } } else { panic!("unexpected char {}", first); } } #[cfg(test)] mod tests { use super::*; fn count(cs: &str, runs: Vec, expected: usize) { let cs = cs.chars().collect::>(); assert_eq!( count_arrangements( CountInputs { cs: &cs, assuming_first_char: None, in_run: false, runs, }, &mut HashMap::new(), ), expected ); } #[test] fn test1() { count("???.###", vec![1, 1, 3], 1); } #[test] fn test2() { count(".??..??...?##.", vec![1, 1, 3], 4); } #[test] fn test3() { count("?#?#?#?#?#?#?#?", vec![1, 3, 1, 6], 1); } #[test] fn test4() { count("????.#...#...", vec![4, 1, 1], 1); } #[test] fn test5() { count("????.######..#####", vec![1, 6, 5], 4); } #[test] fn test6() { count("?###????????", vec![3, 2, 1], 10); } fn unfold(cs: &str, mut runs: Vec, expected: usize) { let mut cs = cs.chars().collect::>(); unfold_line(&mut cs, &mut runs); assert_eq!( count_arrangements( CountInputs { cs: &cs, assuming_first_char: None, in_run: false, runs, }, &mut HashMap::new() ), expected ); } #[test] fn unfolded() { unfold("???.###", vec![1, 1, 3], 1); unfold(".??..??...?##.", vec![1, 1, 3], 16384); unfold("?#?#?#?#?#?#?#?", vec![1, 3, 1, 6], 1); unfold("????.#...#...", vec![4, 1, 1], 16); unfold("????.######..#####.", vec![1, 6, 5], 2500); unfold("?###????????", vec![3, 2, 1], 506250); } }