dynamic programming
This commit is contained in:
parent
c41e546be5
commit
1c368042c2
@ -2,7 +2,16 @@
|
|||||||
name = "day12"
|
name = "day12"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
default-run = "dp"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "old"
|
||||||
|
path= "src/main.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "dp"
|
||||||
|
path= "src/main_dp.rs"
|
||||||
|
243
day12/src/main_dp.rs
Normal file
243
day12/src/main_dp.rs
Normal file
@ -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<u64>> = 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::<Vec<_>>();
|
||||||
|
println!("{} {:?}", row.iter().map(
|
||||||
|
|&x| match x {
|
||||||
|
0 => '0', u64::MAX => '.', _ => 'X'
|
||||||
|
}).rev().collect::<String>(), &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::<Vec<_>>();
|
||||||
|
println!("{:?} {:?}", row.iter().map(
|
||||||
|
|&x| x as i64).rev().collect::<Vec<_>>(), &broken_sequences[broken_sequences.len() - j..]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn use_or_update<F: Fn(&mut Vec<Vec<u64>>) -> u64>(cache: &mut Vec<Vec<u64>>, 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<Vec<u64>>) -> 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<Vec<u64>>) -> 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::<Vec<_>>();
|
||||||
|
let layout2 = layout_str.bytes().chain(iter::once(b'?')).cycle().take(layout.len() * 5 + 4).collect::<Vec<_>>();
|
||||||
|
let numbers = split_whitespace.next().unwrap()
|
||||||
|
.split(',').map(|str| str.parse::<u32>().unwrap()).collect::<Vec<_>>();
|
||||||
|
let numbers2 = numbers.iter().cycle().take(numbers.len() * 5).copied().collect::<Vec<_>>();
|
||||||
|
let sum_numbers = numbers.iter().sum::<u32>();
|
||||||
|
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);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user