diff --git a/samples/day12.txt b/samples/day12.txt new file mode 100644 index 0000000..e925935 --- /dev/null +++ b/samples/day12.txt @@ -0,0 +1,6 @@ +???.### 1,1,3 +.??..??...?##. 1,1,3 +?#?#?#?#?#?#?#? 1,3,1,6 +????.#...#... 4,1,1 +????.######..#####. 1,6,5 +?###???????? 3,2,1 diff --git a/src/bin/day12.rs b/src/bin/day12.rs new file mode 100644 index 0000000..bb3affd --- /dev/null +++ b/src/bin/day12.rs @@ -0,0 +1,230 @@ +use std::{collections::VecDeque, str::FromStr}; + +use indicatif::ParallelProgressIterator; +use memoize::memoize; +use rayon::prelude::*; + +use aoc_2023::*; + +type Output1 = usize; +type Output2 = Output1; + +#[derive(Debug)] +struct Row { + map: VecDeque, + damaged: VecDeque, + // cache: HashMap<(usize, usize, bool), usize>, +} + +impl FromStr for Row { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + let (map, damaged) = s.split_once(' ').unwrap(); + + let map = map.chars().collect(); + let damaged = parse_separated(',', damaged); + + Ok(Row::new(map, damaged)) + } +} + +#[memoize] +fn arrangements( + mut map: VecDeque, + mut damaged: VecDeque, + mut in_damaged: bool, +) -> usize { + // all damaged have been placed + if damaged.is_empty() { + return if map.iter().all(|&c| c == '.' || c == '?') { + 1 + } else { + 0 + }; + } + + // skip the functional + while let Some('.') = map.front() { + if in_damaged { + return 0; + } + + in_damaged = false; + map.pop_front(); + } + + if map.is_empty() || damaged.is_empty() { + return 0; + } + + match map[0] { + '?' => { + map[0] = '#'; + let as_non_functional = arrangements(map.clone(), damaged.clone(), true); + + map[0] = '.'; + let as_functional = if !in_damaged { + arrangements(map, damaged, false) + } else { + 0 + }; + + as_functional + as_non_functional + } + '#' if damaged.is_empty() => 0, + '#' => { + damaged[0] -= 1; + + if damaged[0] == 0 { + if 1 < map.len() && map[1] == '#' { + 0 + } else if 1 < map.len() { + map[1] = '.'; + arrangements( + map.iter().cloned().skip(1).collect(), + damaged.iter().cloned().skip(1).collect(), + false, + ) + } else { + let result = arrangements( + map.iter().cloned().skip(2).collect(), + damaged.iter().cloned().skip(1).collect(), + false, + ); + result + } + } else { + let result = arrangements(map.iter().cloned().skip(1).collect(), damaged, true); + result + } + } + _ => unreachable!(), + } +} + +impl Row { + fn new(map: Vec, damaged: Vec) -> Row { + Row { + map: VecDeque::from(map), + damaged: VecDeque::from(damaged), + // cache: HashMap::new(), + } + } + + fn arrangements(&mut self) -> usize { + arrangements(self.map.clone(), self.damaged.clone(), false) + } + + fn unfold(&self) -> Row { + Row::new( + vec![self.map.iter().collect::(); 5] + .join("?") + .chars() + .collect_vec(), + self.damaged + .iter() + .cloned() + .cycle() + .take(5 * self.damaged.len()) + .collect_vec(), + ) + } +} + +struct Day12 { + rows: Vec, +} +impl Solution for Day12 { + fn new>(pathname: P) -> Self { + Self { + rows: file_to_structs(pathname), + } + } + + fn part_1(&mut self) -> Output1 { + self.rows.iter_mut().map(|r| r.arrangements()).sum() + } + + fn part_2(&mut self) -> Output2 { + self.rows + .par_iter() + .progress() + .map(|r| r.unfold().arrangements()) + .sum() + } +} + +fn main() -> Result<()> { + // Day12::run("sample") + Day12::main() +} + +test_sample!(day_12, Day12, 21, 525152); + +#[cfg(test)] +mod day_12_extended { + use super::*; + + #[test] + fn arrangements_1() { + let mut row = Row::new("???.###".chars().collect_vec(), vec![1, 1, 3]); + + assert_eq!(row.arrangements(), 1); + assert_eq!(row.unfold().arrangements(), 1); + } + + #[test] + fn arrangements_2() { + let mut row = Row::new(".??..??...?##.".chars().collect_vec(), vec![1, 1, 3]); + + assert_eq!(row.arrangements(), 4); + assert_eq!(row.unfold().arrangements(), 16384); + } + + #[test] + fn arrangements_3() { + let mut row = Row::new("?#?#?#?#?#?#?#?".chars().collect_vec(), vec![1, 3, 1, 6]); + + assert_eq!(row.arrangements(), 1); + assert_eq!(row.unfold().arrangements(), 1); + } + + #[test] + fn arrangements_4() { + let mut row = Row::new("????.#...#...".chars().collect_vec(), vec![4, 1, 1]); + + assert_eq!(row.arrangements(), 1); + assert_eq!(row.unfold().arrangements(), 16); + } + + #[test] + fn arrangements_5() { + let mut row = Row::new("????.######..#####.".chars().collect_vec(), vec![1, 6, 5]); + + assert_eq!(row.arrangements(), 4); + assert_eq!(row.unfold().arrangements(), 2500); + } + + #[test] + fn arrangements_6() { + let mut row = Row::new("?###????????".chars().collect_vec(), vec![3, 2, 1]); + + assert_eq!(row.arrangements(), 10); + assert_eq!(row.unfold().arrangements(), 506250); + } + + #[test] + fn arrangements_7() { + let mut row = Row::new("????????????".chars().collect_vec(), vec![3, 2, 1]); + + assert_eq!(row.arrangements(), 35); + } + + #[test] + fn arrangements_8() { + let mut row = Row::new("???#?#?????.?##??#".chars().collect_vec(), vec![8, 6]); + + assert_eq!(row.arrangements(), 4); + } +}