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.
This commit is contained in:
Keenan Tims 2023-12-12 02:20:53 -08:00
parent 20e2a9f7be
commit a431c4b75f
Signed by: ktims
GPG Key ID: 11230674D69038D4

View File

@ -1,6 +1,10 @@
use itertools::Itertools; use itertools::Itertools;
use rayon::prelude::*;
use std::cell::RefCell;
use std::collections::HashMap;
use std::fs::File; use std::fs::File;
use std::io::{BufRead, BufReader, Lines}; use std::io::{BufRead, BufReader, Lines};
use std::time::{Duration, Instant};
// BOILERPLATE // BOILERPLATE
type InputIter = Lines<BufReader<File>>; type InputIter = Lines<BufReader<File>>;
@ -12,29 +16,38 @@ fn get_input() -> InputIter {
} }
fn main() { fn main() {
println!("Problem 1 solution: {}", problem1(get_input())); let start = Instant::now();
println!("Problem 2 solution: {}", problem2(get_input())); 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 // PARSE
#[derive(Debug)] #[derive(Debug)]
struct SpringRow { struct SpringRow<'a> {
springs: Vec<char>, springs: Vec<char>,
spans: Vec<usize>, spans: Vec<usize>,
cache: RefCell<HashMap<(usize, &'a [usize], usize), u64>>,
} }
impl From<&str> for SpringRow { impl<'a> From<&str> for SpringRow<'a> {
fn from(s: &str) -> Self { fn from(s: &str) -> Self {
let (springs_s, spans_s) = s.split_once(' ').unwrap(); let (springs_s, spans_s) = s.split_once(' ').unwrap();
SpringRow { SpringRow {
springs: springs_s.chars().collect(), springs: springs_s.chars().collect(),
spans: spans_s.split(',').map(|x| x.parse().unwrap()).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 { fn possible_arrangements(&self) -> u64 {
let mut permutations = vec![self.springs.clone()]; let mut permutations = vec![self.springs.clone()];
while let Some(cur) = permutations.iter().position(|perm| perm.contains(&'?')) { while let Some(cur) = permutations.iter().position(|perm| perm.contains(&'?')) {
@ -66,6 +79,83 @@ impl SpringRow {
}) })
.count() as u64 .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 // PROBLEM 1 solution
@ -75,12 +165,32 @@ fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
.map(|row| SpringRow::from(row.unwrap().as_str())) .map(|row| SpringRow::from(row.unwrap().as_str()))
.collect(); .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 // PROBLEM 2 solution
fn problem2<T: BufRead>(input: Lines<T>) -> u64 { fn problem2<T: BufRead>(input: Lines<T>) -> u64 {
0 let mut rows: Vec<SpringRow> = 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)] #[cfg(test)]
@ -104,6 +214,6 @@ mod tests {
#[test] #[test]
fn problem2_example() { fn problem2_example() {
let c = Cursor::new(EXAMPLE); let c = Cursor::new(EXAMPLE);
assert_eq!(problem2(c.lines()), 0); assert_eq!(problem2(c.lines()), 525152);
} }
} }