Compare commits

..

2 Commits

Author SHA1 Message Date
Acvaxoort
b5943d5ce5 probably made it a bit faster 2023-12-13 01:42:41 +01:00
Acvaxoort
ba7b8521cd day 12 2023-12-13 01:40:43 +01:00
4 changed files with 1262 additions and 0 deletions

7
day12/Cargo.lock generated Normal file
View File

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "day12"
version = "0.1.0"

8
day12/Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "day12"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

1000
day12/input.txt Normal file

File diff suppressed because it is too large Load Diff

247
day12/src/main.rs Normal file
View File

@ -0,0 +1,247 @@
use std::fs::read_to_string;
use std::time::Instant;
use std::iter;
// 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(layout: &[u8], broken_sequences: &[u32], sum_broken: u32) -> 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 += count_options(&layout[split_position + 1..], broken_sequences, sum_broken);
}
// 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 = count_options(
&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(
&layout[split_position - last_offset..],
&broken_sequences[last_taken_index..], sum_broken - sum_elems_taken_no_last);
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(layout: &[u8], broken_sequences: &[u32], sum_broken: u32) -> 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
count_options(&layout[sequence_position..], &broken_sequences[1..],
sum_broken - broken_sequences[0])
}
// Generate all configurations of replacing '?' with '#' or '.'
fn generate_naive_options(layout: &[u8]) -> Vec<Vec<u8>> {
let mut split_position = 0;
while split_position < layout.len() && layout[split_position] != b'?' {
split_position += 1;
}
if split_position == layout.len() {
return vec![Vec::from(layout)];
}
let mut prefix1 = Vec::from(&layout[..=split_position]);
let mut prefix2 = Vec::from(&layout[..=split_position]);
prefix1[split_position] = b'#';
prefix2[split_position] = b'.';
let mut merged: Vec<Vec<u8>> = Vec::new();
let options = generate_naive_options(&layout[split_position + 1..]);
for option in options {
let mut prefix1_clone = prefix1.clone();
let mut prefix2_clone = prefix2.clone();
prefix1_clone.extend_from_slice(&option);
prefix2_clone.extend_from_slice(&option);
merged.push(prefix1_clone);
merged.push(prefix2_clone);
}
merged
}
// Generate all configurations and test them
fn count_naive_options(layout: &[u8], broken_sequences: &[u32]) -> u32 {
let options = generate_naive_options(&layout);
options.iter().filter(|&option| {
let mut position = 0;
for &length in broken_sequences {
while position < option.len() && option[position] == b'.' {
position += 1;
}
let target_position = position + length as usize;
if target_position > option.len() {
return false;
}
while position < target_position && option[position] == b'#' {
position += 1;
}
if position != target_position {
return false;
}
if position < option.len() {
if option[position] == b'#' {
return false;
}
position += 1;
}
}
while position < option.len() {
if option[position] != b'.' {
return false;
}
position += 1;
}
true
}).count() as u32
}
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(&layout, &numbers, sum_numbers) as u64;
sum1 += options;
sum2 += count_options(&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);
}