From a431c4b75f83465d590cea69bffd65e742d05cf9 Mon Sep 17 00:00:00 2001 From: Keenan Tims Date: Tue, 12 Dec 2023 02:20:53 -0800 Subject: [PATCH] day12: problem 2 solution what a doozy. iterative solution from part 1 totally untenable. recursive solution a bit tricky, but still not fast enough for brute force. had to implement memoization cache for reasonable runtime. --- 12/src/main.rs | 126 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 118 insertions(+), 8 deletions(-) diff --git a/12/src/main.rs b/12/src/main.rs index b9bd11a..e96e328 100644 --- a/12/src/main.rs +++ b/12/src/main.rs @@ -1,6 +1,10 @@ use itertools::Itertools; +use rayon::prelude::*; +use std::cell::RefCell; +use std::collections::HashMap; use std::fs::File; use std::io::{BufRead, BufReader, Lines}; +use std::time::{Duration, Instant}; // BOILERPLATE type InputIter = Lines>; @@ -12,29 +16,38 @@ fn get_input() -> InputIter { } fn main() { - println!("Problem 1 solution: {}", problem1(get_input())); - println!("Problem 2 solution: {}", problem2(get_input())); + let start = Instant::now(); + let ans1 = problem1(get_input()); + let duration = start.elapsed(); + println!("Problem 1 solution: {} [{}s]", ans1, duration.as_secs_f64()); + + let start = Instant::now(); + let ans2 = problem2(get_input()); + let duration = start.elapsed(); + println!("Problem 2 solution: {} [{}s]", ans2, duration.as_secs_f64()); } // PARSE #[derive(Debug)] -struct SpringRow { +struct SpringRow<'a> { springs: Vec, spans: Vec, + cache: RefCell>, } -impl From<&str> for SpringRow { +impl<'a> From<&str> for SpringRow<'a> { fn from(s: &str) -> Self { let (springs_s, spans_s) = s.split_once(' ').unwrap(); SpringRow { springs: springs_s.chars().collect(), spans: spans_s.split(',').map(|x| x.parse().unwrap()).collect(), + cache: RefCell::new(HashMap::new()), } } } -impl SpringRow { +impl<'a> SpringRow<'a> { fn possible_arrangements(&self) -> u64 { let mut permutations = vec![self.springs.clone()]; while let Some(cur) = permutations.iter().position(|perm| perm.contains(&'?')) { @@ -66,6 +79,83 @@ impl SpringRow { }) .count() as u64 } + fn memoize_arrangements_r( + &self, + idx: usize, + spans_left: &'a [usize], + cur_span_left: usize, + ) -> u64 { + if let Some(value) = self.cache.borrow().get(&(idx, spans_left, cur_span_left)) { + return *value; + } + let result = self.possible_arrangements_r(idx, spans_left, cur_span_left); + self.cache + .borrow_mut() + .insert((idx, spans_left, cur_span_left), result); + result + } + fn possible_arrangements_r( + &self, + idx: usize, + spans_left: &'a [usize], + cur_span_left: usize, + ) -> u64 { + if cur_span_left == 0 && spans_left.len() == 1 { + // everything is consumed. if we're at the end, we good + // if we have stuff left, as long as it doesn't contain a fixed broken spring we good + if idx == self.springs.len() || !self.springs[idx..].contains(&'#') { + // println!("{:?} valid", self.springs); + return 1; + } + // something invalid + return 0; + } + if idx >= self.springs.len() { + // reached the end of the string without consuming all our spans + // println!( + // "{:?} invalid idx: {} spans_left: {:?}", + // self.springs, idx, spans_left + // ); + return 0; + } + // println!( + // "call idx: {} str: {:?} spans_left: {:?} cur_span_left: {}", + // idx, + // &self.springs[idx..], + // spans_left, + // cur_span_left + // ); + return if cur_span_left == 0 { + match self.springs[idx] { + '#' => 0, // a broken spring after we have completed our span is invalid + // a space or a ? (becomes a space) starts a new span + '.' | '?' => self.memoize_arrangements_r(idx + 1, &spans_left[1..], spans_left[1]), + _ => panic!("invalid spring"), + } + } else { + // we are mid span + match self.springs[idx] { + // cur_span_left != 0, so we still have work to do with broken springs + '#' => self.memoize_arrangements_r(idx + 1, spans_left, cur_span_left - 1), + '.' => { + if spans_left[0] == cur_span_left { + self.memoize_arrangements_r(idx + 1, spans_left, cur_span_left) + } else { + 0 + } + } + '?' => { + if spans_left[0] == cur_span_left { + self.memoize_arrangements_r(idx + 1, spans_left, cur_span_left - 1) + + self.memoize_arrangements_r(idx + 1, spans_left, cur_span_left) + } else { + self.memoize_arrangements_r(idx + 1, spans_left, cur_span_left - 1) + } + } + _ => panic!("invalid spring"), + } + }; + } } // PROBLEM 1 solution @@ -75,12 +165,32 @@ fn problem1(input: Lines) -> u64 { .map(|row| SpringRow::from(row.unwrap().as_str())) .collect(); - rows.iter().map(|row| row.possible_arrangements()).sum() + rows.iter() + .map(|row| row.possible_arrangements_r(0, &row.spans, row.spans[0])) + .sum() } // PROBLEM 2 solution fn problem2(input: Lines) -> u64 { - 0 + let mut rows: Vec = input + .map(|row| SpringRow::from(row.unwrap().as_str())) + .collect(); + + for row in &mut rows { + let orig_slice = &row.springs.clone(); + for _i in 0..4 { + row.springs.push('?'); + row.springs.extend_from_slice(orig_slice); + } + + let orig_span_slice = &row.spans.clone(); + for _i in 0..4 { + row.spans.extend_from_slice(orig_span_slice); + } + } + rows.iter() + .map(|row| row.possible_arrangements_r(0, &row.spans, row.spans[0])) + .sum() } #[cfg(test)] @@ -104,6 +214,6 @@ mod tests { #[test] fn problem2_example() { let c = Cursor::new(EXAMPLE); - assert_eq!(problem2(c.lines()), 0); + assert_eq!(problem2(c.lines()), 525152); } }