diff --git a/2023/src/day12.rs b/2023/src/day12.rs new file mode 100644 index 0000000..0276a2f --- /dev/null +++ b/2023/src/day12.rs @@ -0,0 +1,208 @@ +use aoc_runner_derive::{aoc, aoc_generator}; +use std::collections::HashMap; +use std::str::FromStr; + +type Cache = HashMap<(Vec, Vec), usize>; + +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +enum Condition { + Unknown, + Functional, + Damaged, +} + +impl FromStr for Condition { + type Err = String; + fn from_str(input: &str) -> Result { + match input { + "?" => Ok(Condition::Unknown), + "." => Ok(Condition::Functional), + "#" => Ok(Condition::Damaged), + n @ _ => Err(format!("Unknown character '{}'", n)), + } + } +} + +#[derive(Clone)] +struct Spring { + list: Vec, + groups: Vec, +} + +#[aoc_generator(day12)] +fn parse(input: &str) -> Vec { + input + .lines() + .filter_map(|line| { + if !line.is_empty() { + let parts = line.split(" ").collect::>(); + let list = parts[0] + .split("") + .filter_map(|e| { + if e.is_empty() { + None + } else { + Condition::from_str(e).ok() + } + }) + .collect(); + let groups = parts[1] + .split(",") + .map(|e| e.parse::().unwrap()) + .collect(); + Some(Spring { list, groups }) + } else { + None + } + }) + .collect() +} + +fn calc_solutions(list: &Vec, groups: &Vec, cache: &mut Cache) -> usize { + if list.is_empty() { + if groups.is_empty() { + return 1; + } else { + return 0; + } + } + + match list[0] { + Condition::Functional => calc_solutions(&list[1..].to_vec(), groups, cache), + Condition::Damaged => calc_damaged_solutions(list, groups, cache), + Condition::Unknown => { + calc_solutions(&list[1..].to_vec(), groups, cache) + + calc_damaged_solutions(list, groups, cache) + } + } +} + +fn calc_damaged_solutions(list: &Vec, groups: &Vec, cache: &mut Cache) -> usize { + if let Some(&result) = cache.get(&(list.clone(), groups.clone())) { + return result; + } + + if groups.is_empty() { + return 0; + } + + if list.len() < groups[0] { + return 0; + } + + for i in 0..groups[0] { + if list[i] == Condition::Functional { + return 0; + } + } + + if list.len() == groups[0] { + if groups.len() == 1 { + return 1; + } + return 0; + } + if list[groups[0]] == Condition::Damaged { + return 0; + } + let result = calc_solutions( + &list[(groups[0] + 1)..].to_vec(), + &groups[1..].to_vec(), + cache, + ); + cache.insert((list.clone(), groups.clone()), result); + result +} + +#[aoc(day12, part1)] +fn part1(input: &[Spring]) -> usize { + let mut result: usize = 0; + for src in input.iter() { + let mut cache: Cache = HashMap::new(); + result += calc_solutions(&src.list, &src.groups, &mut cache); + } + result +} + +#[aoc(day12, part2)] +fn part2(input: &[Spring]) -> usize { + let mut result: usize = 0; + for src in input.iter() { + let mut l: Vec = Vec::with_capacity(src.groups.len() * 5 + 5); + let mut g: Vec = Vec::with_capacity(src.list.len() * 5); + for index in 0..5 { + l.append(&mut src.list.clone()); + if index < 4 { + l.push(Condition::Unknown); + } + g.append(&mut src.groups.clone()); + } + let mut cache: Cache = HashMap::new(); + let s = calc_solutions(&l, &g, &mut cache); + result += s; + } + result +} + +#[cfg(test)] +mod tests { + use super::*; + + const SAMPLE_DATA: &'static str = r#"???.### 1,1,3 +.??..??...?##. 1,1,3 +?#?#?#?#?#?#?#? 1,3,1,6 +????.#...#... 4,1,1 +????.######..#####. 1,6,5 +?###???????? 3,2,1 +"#; + + #[test] + fn sample_data_part1() { + let input = parse(&SAMPLE_DATA); + assert_eq!(part1(&input), 21); + } + + #[test] + fn sample_data_3() { + let line1 = Spring { + list: vec![ + Condition::Unknown, + Condition::Unknown, + Condition::Unknown, + Condition::Functional, + Condition::Damaged, + Condition::Damaged, + Condition::Damaged, + ], + groups: vec![1, 1, 3], + }; + assert_eq!(part2(&[line1]), 1); + + let line2 = Spring { + list: vec![ + Condition::Functional, + Condition::Unknown, + Condition::Unknown, + Condition::Functional, + Condition::Functional, + Condition::Unknown, + Condition::Unknown, + Condition::Functional, + Condition::Functional, + Condition::Functional, + Condition::Unknown, + Condition::Damaged, + Condition::Damaged, + Condition::Functional, + ], + groups: vec![1, 1, 3], + }; + assert_eq!(part2(&[line2]), 16384); + } + + #[test] + fn sample_data_part2() { + let input = parse(&SAMPLE_DATA); + assert_eq!(part2(&input), 525152); + } +} diff --git a/2023/src/lib.rs b/2023/src/lib.rs index a5b2fe6..047c882 100644 --- a/2023/src/lib.rs +++ b/2023/src/lib.rs @@ -11,5 +11,6 @@ mod day08; mod day09; mod day10; mod day11; +mod day12; aoc_lib! { year = 2023 }