From 3f2507322abfa15efff092ce8e5f6bc290df651e Mon Sep 17 00:00:00 2001 From: Kevin Caffrey Date: Tue, 12 Dec 2023 15:29:45 -0500 Subject: [PATCH] day 12 --- README.md | 25 +++---- data/examples/12.txt | 6 ++ src/bin/12.rs | 163 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 182 insertions(+), 12 deletions(-) create mode 100644 data/examples/12.txt create mode 100644 src/bin/12.rs diff --git a/README.md b/README.md index fee2ef3..b54099e 100644 --- a/README.md +++ b/README.md @@ -27,19 +27,20 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www. | Day | Part 1 | Part 2 | | :---: | :---: | :---: | -| [Day 1](./src/bin/01.rs) | `34.3µs` | `35.6µs` | -| [Day 2](./src/bin/02.rs) | `42.3µs` | `41.9µs` | -| [Day 3](./src/bin/03.rs) | `84.6µs` | `99.4µs` | +| [Day 1](./src/bin/01.rs) | `35.3µs` | `36.5µs` | +| [Day 2](./src/bin/02.rs) | `42.2µs` | `41.8µs` | +| [Day 3](./src/bin/03.rs) | `84.0µs` | `99.0µs` | | [Day 4](./src/bin/04.rs) | `49.0µs` | `49.5µs` | -| [Day 5](./src/bin/05.rs) | `20.4µs` | `24.2µs` | -| [Day 6](./src/bin/06.rs) | `221.0ns` | `104.0ns` | -| [Day 7](./src/bin/07.rs) | `103.4µs` | `96.2µs` | -| [Day 8](./src/bin/08.rs) | `72.2µs` | `160.0µs` | -| [Day 9](./src/bin/09.rs) | `53.1µs` | `47.1µs` | -| [Day 10](./src/bin/10.rs) | `262.9µs` | `279.8µs` | -| [Day 11](./src/bin/11.rs) | `16.2µs` | `15.8µs` | - -**Total: 1.59ms** +| [Day 5](./src/bin/05.rs) | `20.6µs` | `24.5µs` | +| [Day 6](./src/bin/06.rs) | `203.0ns` | `101.0ns` | +| [Day 7](./src/bin/07.rs) | `93.3µs` | `91.4µs` | +| [Day 8](./src/bin/08.rs) | `74.6µs` | `161.1µs` | +| [Day 9](./src/bin/09.rs) | `58.7µs` | `55.4µs` | +| [Day 10](./src/bin/10.rs) | `264.9µs` | `275.0µs` | +| [Day 11](./src/bin/11.rs) | `16.5µs` | `15.8µs` | +| [Day 12](./src/bin/12.rs) | `129.4µs` | `632.8µs` | + +**Total: 2.35ms** --- diff --git a/data/examples/12.txt b/data/examples/12.txt new file mode 100644 index 0000000..c5bec3a --- /dev/null +++ b/data/examples/12.txt @@ -0,0 +1,6 @@ +???.### 1,1,3 +.??..??...?##. 1,1,3 +?#?#?#?#?#?#?#? 1,3,1,6 +????.#...#... 4,1,1 +????.######..#####. 1,6,5 +?###???????? 3,2,1 \ No newline at end of file diff --git a/src/bin/12.rs b/src/bin/12.rs new file mode 100644 index 0000000..c304832 --- /dev/null +++ b/src/bin/12.rs @@ -0,0 +1,163 @@ +use rayon::{iter::ParallelIterator, str::ParallelString}; +use tinyvec::ArrayVec; + +advent_of_code::solution!(12); + +pub fn part_one(input: &str) -> Option { + Some(input.par_lines().map(|line| solve_line(line, 1)).sum()) +} + +pub fn part_two(input: &str) -> Option { + Some(input.par_lines().map(|line| solve_line(line, 5)).sum()) +} + +fn solve_line(line: &str, copies: usize) -> u64 { + let (spring_records, damaged_counts) = line.split_once(' ').expect("should be valid input"); + let spring_records = vec![spring_records; copies].join("?"); + let spring_records = spring_records.as_bytes(); + + let damaged_counts = std::iter::repeat( + damaged_counts + .split(',') + .map(|s| s.parse::().expect("should be valid spring count")), + ) + .take(copies) + .flatten() + .collect::>(); + + // solve_brute_force_recursion(spring_records, &damaged_counts, 0) + solve_dp(spring_records, &damaged_counts) +} + +fn solve_dp(spring_records: &[u8], damaged_counts: &[u32]) -> u64 { + // Precompute how many springs could be damaged in a row ending at each spring. + let mut damaged_runs = vec![0; spring_records.len()]; + let mut cur_damaged_run = 0; + let mut first_damaged = spring_records.len(); + for (i, record) in spring_records.iter().copied().enumerate() { + if record != b'.' { + cur_damaged_run += 1; + } else { + cur_damaged_run = 0; + } + if record == b'#' && i < first_damaged { + first_damaged = i; + } + damaged_runs[i] = cur_damaged_run; + } + + // Solve the problem using DP, for f(M, N) where M is the number of + // damaged springs that have been "resolved" and N is how many springs are used from the input. + let mut prev = vec![0; spring_records.len() + 1]; + let mut cur = vec![0; spring_records.len() + 1]; + (0..=first_damaged).for_each(|i| { + prev[i] = 1; + }); + for (damaged_index, damaged_count) in damaged_counts.iter().copied().enumerate() { + let damaged_count = damaged_count as usize; + cur[0] = 0; + for end_of_spring in 0..spring_records.len() { + let last_record = spring_records[end_of_spring]; + let mut count = 0; + if last_record == b'.' || last_record == b'?' { + // This record can be operational. The count is the same + // as f(damaged_count, end_of_spring). + count += end_of_spring + .checked_sub(0) + .map(|index| cur[index]) + .unwrap_or(0); + } + if last_record == b'#' || last_record == b'?' { + // This record can be counted as damaged. + // First we see if this can even be considered the end of the next run + // of damaged springs. To be considered the end of such a run, the next + // spring cannot be damaged, and the previous springs (corresponding to the + // next damaged count) have to be either unknown or damaged. + // Finally, the character preceding the run of damaged springs must not + // be damaged (so either . or ?). + let next_is_definite_damaged = spring_records + .get(end_of_spring + 1) + .filter(|&&v| v == b'#') + .is_some(); + let has_correct_damaged_count = + !next_is_definite_damaged && damaged_runs[end_of_spring] >= damaged_count; + let has_correct_preceding = has_correct_damaged_count + && end_of_spring + .checked_sub(damaged_count) + .map(|i| spring_records[i]) + .filter(|&v| v == b'#') + .is_none(); + if has_correct_preceding { + count += end_of_spring + .checked_sub(damaged_count) + .map(|index| prev[index]) + .unwrap_or(if damaged_index == 0 { 1 } else { 0 }); + } + } + + cur[end_of_spring + 1] = count; + } + std::mem::swap(&mut prev, &mut cur); + } + prev[spring_records.len()] +} + +#[allow(unused)] +fn solve_brute_force_recursion( + spring_records: &[u8], + damaged_counts: &[u32], + current_damaged_length: u32, +) -> u64 { + if spring_records.is_empty() && damaged_counts.is_empty() && current_damaged_length == 0 + || spring_records.is_empty() + && damaged_counts.len() == 1 + && current_damaged_length == damaged_counts[0] + { + return 1; + } else if spring_records.is_empty() + || damaged_counts.is_empty() && current_damaged_length > 0 + || !damaged_counts.is_empty() && current_damaged_length > damaged_counts[0] + { + return 0; + } + + let mut count = 0; + if spring_records[0] == b'.' || spring_records[0] == b'?' { + // Next record can be operational. + count += if current_damaged_length > 0 { + if current_damaged_length == damaged_counts[0] { + solve_brute_force_recursion(&spring_records[1..], &damaged_counts[1..], 0) + } else { + 0 + } + } else { + solve_brute_force_recursion(&spring_records[1..], damaged_counts, 0) + }; + } + if spring_records[0] == b'#' || spring_records[0] == b'?' { + // Next record can be damaged. + count += solve_brute_force_recursion( + &spring_records[1..], + damaged_counts, + current_damaged_length + 1, + ); + } + count +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(21)); + } + + #[test] + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(525152)); + } +}