From 1c368042c20753653c24dcc1788325f8afcc7992 Mon Sep 17 00:00:00 2001 From: Acvaxoort Date: Wed, 13 Dec 2023 20:43:04 +0100 Subject: [PATCH] dynamic programming --- day12/Cargo.toml | 9 ++ day12/src/main_dp.rs | 243 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 252 insertions(+) create mode 100644 day12/src/main_dp.rs diff --git a/day12/Cargo.toml b/day12/Cargo.toml index ba13763..4f2303f 100644 --- a/day12/Cargo.toml +++ b/day12/Cargo.toml @@ -2,7 +2,16 @@ name = "day12" version = "0.1.0" edition = "2021" +default-run = "dp" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] + +[[bin]] +name = "old" +path= "src/main.rs" + +[[bin]] +name = "dp" +path= "src/main_dp.rs" diff --git a/day12/src/main_dp.rs b/day12/src/main_dp.rs new file mode 100644 index 0000000..1d5c721 --- /dev/null +++ b/day12/src/main_dp.rs @@ -0,0 +1,243 @@ +use std::fs::read_to_string; +use std::time::Instant; +use std::iter; + +const PRINT_VALUES: bool = false; +const PRINT_VISUALISATION: bool = false; + +// Counts all options by advancing through the spring layout from left to right, splitting at +// possible uncertainities. Groups sequences of uncertain values and uses combinatorics. +fn count_options_dp(layout: &[u8], broken_sequences: &[u32], sum_broken: u32) -> u64 { + // Indexed with [a][b], storing results for computation, last a elements of layout + // and last b elements of broken_sequences + // u64::MAX marks values that will never be used and don't need to be computed + let mut cache: Vec> = vec![vec![u64::MAX; broken_sequences.len() + 1]; layout.len() + 1]; + // For empty layout and empty broken sequences there's one option + cache[0][0] = 1; + cache[0][1..].iter_mut().for_each(|x| *x = 0); + // Get the result + let result = count_options_with_cache(layout, broken_sequences, sum_broken, &mut cache); + // Visualise + if PRINT_VISUALISATION { + cache[layout.len()][broken_sequences.len()] = result; + println!("{}", std::str::from_utf8(layout).unwrap()); + for j in 0..broken_sequences.len() + 1 { + let row = cache.iter().map(|row| row[j]).collect::>(); + println!("{} {:?}", row.iter().map( + |&x| match x { + 0 => '0', u64::MAX => '.', _ => 'X' + }).rev().collect::(), &broken_sequences[broken_sequences.len() - j..]); + } + } + if PRINT_VALUES { + cache[layout.len()][broken_sequences.len()] = result; + println!("{}", std::str::from_utf8(layout).unwrap()); + for j in 0..broken_sequences.len() + 1 { + let row = cache.iter().map(|row| row[j]).collect::>(); + println!("{:?} {:?}", row.iter().map( + |&x| x as i64).rev().collect::>(), &broken_sequences[broken_sequences.len() - j..]); + } + } + result +} + +fn use_or_update>) -> u64>(cache: &mut Vec>, idx1: usize, idx2: usize, update_func: F) -> u64 { + if cache[idx1][idx2] == u64::MAX { + cache[idx1][idx2] = update_func(cache); + } + cache[idx1][idx2] +} + +// Counts all options by advancing through the spring layout from left to right, splitting at +// possible uncertainities. Groups sequences of uncertain values and uses combinatorics. +fn count_options_with_cache(layout: &[u8], broken_sequences: &[u32], sum_broken: u32, cache: &mut Vec>) -> u64 { + // Assuming sum_broken must be sum of broken_sequences + // If no more broken need to be placed, the remaining are all unbroken + if sum_broken == 0 { + // If the remaining data contains a surely broken spring, the configuration is impossible + if layout.contains(&b'#') { + return 0; + } + // The rest are not broken, 1 option + return 1; + } + // Go ahead in the layout to find place where a split of options is + let mut split_position = 0; + // Skip through all unbroken + while split_position < layout.len() && layout[split_position] == b'.' { + split_position += 1; + } + // Found no place to fit remaining broken springs, impossible + if layout.len() - split_position < sum_broken as usize + broken_sequences.len() - 1 { + return 0; + } + // Count amount of uncertain springs (can be zero) + let mut num_uncertain: usize = 0; + while split_position < layout.len() && layout[split_position] == b'?' { + split_position += 1; + num_uncertain += 1; + } + if split_position == layout.len() || layout[split_position] == b'.' { + // Block of 1 or more uncertain springs followed by a unbroken spring + let mut sum_options = 0; + // Initially try assuming all the question marks will be unbroken + if split_position < layout.len() { + sum_options += use_or_update( + cache, layout.len() - (split_position + 1), broken_sequences.len(), + |cache| + count_options_with_cache(&layout[split_position + 1..], broken_sequences, sum_broken, cache)); + } + // Taking some number of elements from broken sequences + let mut num_elems_taken = 1; + let mut sum_elems_taken = broken_sequences[0]; + while num_elems_taken <= broken_sequences.len() && sum_elems_taken as usize + num_elems_taken - 1 <= num_uncertain { + // If the split was done due to end of input, it's the length, otherwise advance by 1 + let corrected_split_position = std::cmp::min(split_position + 1, layout.len()); + // Multiplying the combination of elements before with recursive options after + let pre_split_options = count_options_in_uncertain(num_uncertain as u64, sum_elems_taken as u64, num_elems_taken as u64); + + let post_split_options = use_or_update( + cache, layout.len() - corrected_split_position, broken_sequences.len() - num_elems_taken, + |cache| count_options_with_cache( + &layout[corrected_split_position..], + &broken_sequences[num_elems_taken..], sum_broken - sum_elems_taken, cache)); + // let post_split_options = count_options_with_cache( + // &layout[corrected_split_position..], + // &broken_sequences[num_elems_taken..], sum_broken - sum_elems_taken); + sum_options += pre_split_options * post_split_options; + // Prepare for the next iteration + if num_elems_taken < broken_sequences.len() { + sum_elems_taken += broken_sequences[num_elems_taken]; + } + num_elems_taken += 1; + } + return sum_options; + } else { + // Block of 0 or more uncertain springs followed by at least one broken spring + let mut sum_options = 0; + // Count how many known broken elements there are after the uncertain + let mut last_min_length = 0; + while split_position < layout.len() && layout[split_position] == b'#' { + split_position += 1; + last_min_length += 1; + } + let extended_num_uncertain = num_uncertain + last_min_length; + // Taking some number of elements from broken sequences + let mut last_taken_index = 0; + let mut sum_elems_taken_no_last: u32 = 0; + while last_taken_index < broken_sequences.len() { + // Length of last element that is taken, we need to fit it around the end of the region + let last_length = broken_sequences[last_taken_index]; + // Putting the last elem at some offset, subtracted from split_position + let mut last_offset = last_min_length; + while last_offset <= last_length as usize && last_offset <= extended_num_uncertain { + // If sum of taken elements with free spaces (last_taken_index) doesn't fit in the + // uncertainity region + if sum_elems_taken_no_last as usize + last_taken_index > extended_num_uncertain - last_offset { + break; + } + // Multiplying the combination of elements before with recursive options after + let pre_split_options; + if extended_num_uncertain > last_offset + 1 { + pre_split_options = count_options_in_uncertain( + (extended_num_uncertain - last_offset - 1) as u64, sum_elems_taken_no_last as u64, last_taken_index as u64); + } else { + pre_split_options = 1; + } + let post_split_options = count_options_consume_sequence_with_cache( + &layout[split_position - last_offset..], &broken_sequences[last_taken_index..], + sum_broken - sum_elems_taken_no_last, cache); + sum_options += pre_split_options * post_split_options; + // Prepare for the next iteration + last_offset += 1; + } + // Prepare for the next iteration + sum_elems_taken_no_last += broken_sequences[last_taken_index]; + last_taken_index += 1; + } + return sum_options; + } +} + +fn choose(n: u64, k: u64) -> u64 { + let mut prod = 1; + let mut n_copy = n; + for i in 1..=k { + prod *= n_copy; + n_copy -= 1; + prod /= i; + } + prod +} + +fn count_options_in_uncertain(length_uncertain: u64, sum_broken: u64, num_broken: u64) -> u64 { + let slots = length_uncertain - sum_broken + 1; + // slots choose num_broken + if slots == 0 || num_broken == 0 { + return 1; + } + choose(slots, num_broken) +} + +// Helper function for count_options, assuming that a sequence of broken springs starts at +// the beginning of the slice and that sum_broken is sum of broken_sequences and is not 0 +fn count_options_consume_sequence_with_cache(layout: &[u8], broken_sequences: &[u32], sum_broken: u32, cache: &mut Vec>) -> u64 { + let mut sequence_position = 0usize; + let target_position = sequence_position + broken_sequences[0] as usize; + // If we'd run out of space trying to process this option, impossible + if target_position > layout.len() { + return 0; + } + // Go through all potentially broken elements + while sequence_position < target_position && layout[sequence_position] != b'.' { + sequence_position += 1; + } + // If found a surely unbroken element before end of sequence, impossible + if sequence_position < target_position { + return 0; + } + // If we aren't at the end of sequence + if sequence_position < layout.len() { + // If there's yet another surely broken spring, the sequence would be too long, impossible + if layout[sequence_position] == b'#' { + return 0; + } + // If there's a following unbroken element, advance through that because count_options + // assumes with each call that we're starting from a fresh potential sequence + sequence_position += 1; + } + // Call count_options recursively with advanced layout options and consumed one sequence + use_or_update( + cache, layout.len() - sequence_position, broken_sequences.len() - 1, + |cache| + count_options_with_cache(&layout[sequence_position..], &broken_sequences[1..], + sum_broken - broken_sequences[0], cache)) +} + +fn main() { + let time_start = Instant::now(); + let input_str = read_to_string("input.txt").unwrap(); + let time_start_no_io = Instant::now(); + let mut sum1 = 0u64; + let mut sum2 = 0u64; + for line in input_str.lines() { + let mut split_whitespace = line.split_whitespace(); + let layout_str = split_whitespace.next().unwrap(); + let layout = layout_str.bytes().collect::>(); + let layout2 = layout_str.bytes().chain(iter::once(b'?')).cycle().take(layout.len() * 5 + 4).collect::>(); + let numbers = split_whitespace.next().unwrap() + .split(',').map(|str| str.parse::().unwrap()).collect::>(); + let numbers2 = numbers.iter().cycle().take(numbers.len() * 5).copied().collect::>(); + let sum_numbers = numbers.iter().sum::(); + let sum_numbers2 = sum_numbers * 5; + let options = count_options_dp(&layout, &numbers, sum_numbers) as u64; + sum1 += options; + sum2 += count_options_dp(&layout2, &numbers2, sum_numbers2) as u64; + } + let elapsed = time_start.elapsed().as_micros(); + let elapsed_no_io = time_start_no_io.elapsed().as_micros(); + println!("Time: {}us", elapsed); + println!("Time without file i/o: {}us", elapsed_no_io); + println!("Sum1: {}", sum1); + println!("Sum2: {}", sum2); +}