This commit is contained in:
Shadowfacts 2023-12-12 13:52:54 -05:00
parent 3928623854
commit 72f7b2b42c
3 changed files with 1308 additions and 1 deletions

1000
input/day12.txt Normal file

File diff suppressed because it is too large Load Diff

306
src/day12.rs Normal file
View File

@ -0,0 +1,306 @@
use std::collections::HashMap;
pub fn run() {
let input = include_str!("../input/day12.txt");
// let input = r#"???.### 1,1,3
// .??..??...?##. 1,1,3
// ?#?#?#?#?#?#?#? 1,3,1,6
// ????.#...#... 4,1,1
// ????.######..#####. 1,6,5
// ?###???????? 3,2,1"#;
let mut lines = input
.trim()
.lines()
.map(|l| parse_line(l))
.collect::<Vec<_>>();
let count_sum = lines
.iter()
.map(|(cs, runs)| {
count_arrangements(
CountInputs {
cs: &cs,
assuming_first_char: None,
in_run: false,
runs: runs.clone(),
},
&mut HashMap::new(),
)
})
.sum::<usize>();
dbg!(count_sum);
for l in lines.iter_mut() {
unfold_line(&mut l.0, &mut l.1);
}
let count_sum2 = lines
.iter()
.map(|(cs, runs)| {
let res = count_arrangements(
CountInputs {
cs: &cs,
assuming_first_char: None,
in_run: false,
runs: runs.clone(),
},
&mut HashMap::new(),
);
println!("finished line");
res
})
.sum::<usize>();
dbg!(count_sum2);
}
fn parse_line(s: &str) -> (Vec<char>, Vec<u8>) {
let (l, r) = s.trim().split_once(" ").unwrap();
(
l.chars().collect(),
r.split(",").map(|s| s.parse().unwrap()).collect(),
)
}
fn unfold_line(cs: &mut Vec<char>, runs: &mut Vec<u8>) {
let orig_cs = cs.to_vec();
let orig_runs = runs.to_vec();
for _ in 0..4 {
cs.push('?');
cs.extend(orig_cs.iter());
runs.extend(orig_runs.iter());
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct CountInputs<'a> {
cs: &'a [char],
assuming_first_char: Option<char>,
in_run: bool,
runs: Vec<u8>,
}
fn count_arrangements<'a: 'b, 'b>(
inputs: CountInputs<'a>,
// cs: &[char],
// assuming_first_char: Option<char>,
// // mut accepted: Vec<char>,
// in_run: bool,
// runs: &[u8],
cache: &mut HashMap<CountInputs<'b>, usize>,
) -> usize {
if let Some(result) = cache.get(&inputs) {
return *result;
}
if inputs.runs.is_empty() {
assert!(!inputs.in_run);
for &c in inputs.cs {
if c == '#' {
// invalid, since there are remaining broken springs with no broken runs
cache.insert(inputs, 0);
return 0;
} else {
// accepted.push('.');
}
}
// valid
// println!("{}", accepted.iter().collect::<String>());
cache.insert(inputs, 1);
return 1;
}
if inputs.cs.is_empty() {
// if we're out of chars, valid iff there are no runs remaining
if inputs.runs.is_empty() {
assert!(!inputs.in_run);
cache.insert(inputs, 1);
return 1;
} else {
cache.insert(inputs, 0);
return 0;
}
}
let first = inputs.assuming_first_char.unwrap_or(inputs.cs[0]);
if first == '#' {
if inputs.runs[0] == 1 {
// current run should be finished
if inputs.cs.len() > 1 {
if inputs.cs[1] == '#' {
// if the next char is #, invalid b/c the current run would continue
return 0;
} else if inputs.cs[1] == '.' || inputs.cs[1] == '?' {
// let mut accepted = accepted.to_vec();
// accepted.push('#');
// accepted.push('.');
// current run does indeed end
return count_arrangements(
CountInputs {
cs: &inputs.cs[2..],
assuming_first_char: None,
in_run: false,
runs: inputs.runs[1..].iter().copied().collect::<Vec<_>>(),
},
cache,
);
} else {
panic!("invalid char {}", inputs.cs[1]);
}
} else {
// end of string, valid iff there are no more runs
if inputs.runs.len() == 1 {
// println!(
// "{}#, last run: {}",
// accepted.iter().collect::<String>(),
// runs[0]
// );
cache.insert(inputs, 1);
return 1;
} else {
cache.insert(inputs, 0);
return 0;
}
}
} else {
// accepted.push('#');
// current run continues
let mut runs_copy = inputs.runs.to_vec();
runs_copy[0] -= 1;
// println!(
// "{}, current run continuing, {:?}",
// accepted.iter().collect::<String>(),
// runs_copy
// );
return count_arrangements(
CountInputs {
cs: &inputs.cs[1..],
assuming_first_char: None,
in_run: true,
runs: runs_copy,
},
cache,
);
}
} else if first == '?' {
// let mut accepted_dot = accepted.clone();
// accepted_dot.push('.');
let res = count_arrangements(
CountInputs {
cs: inputs.cs,
assuming_first_char: Some('.'),
in_run: inputs.in_run,
runs: inputs.runs.clone(),
},
cache,
) + count_arrangements(
CountInputs {
cs: inputs.cs,
assuming_first_char: Some('#'),
in_run: inputs.in_run,
runs: inputs.runs.clone(),
},
cache,
);
cache.insert(inputs, res);
return res;
} else if first == '.' {
if inputs.in_run {
// the current run ended prematurely
cache.insert(inputs, 0);
return 0;
} else {
// accepted.push('.');
return count_arrangements(
CountInputs {
cs: &inputs.cs[1..],
assuming_first_char: None,
in_run: false,
runs: inputs.runs,
},
cache,
);
}
} else {
panic!("unexpected char {}", first);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn count(cs: &str, runs: Vec<u8>, expected: usize) {
let cs = cs.chars().collect::<Vec<_>>();
assert_eq!(
count_arrangements(
CountInputs {
cs: &cs,
assuming_first_char: None,
in_run: false,
runs,
},
&mut HashMap::new(),
),
expected
);
}
#[test]
fn test1() {
count("???.###", vec![1, 1, 3], 1);
}
#[test]
fn test2() {
count(".??..??...?##.", vec![1, 1, 3], 4);
}
#[test]
fn test3() {
count("?#?#?#?#?#?#?#?", vec![1, 3, 1, 6], 1);
}
#[test]
fn test4() {
count("????.#...#...", vec![4, 1, 1], 1);
}
#[test]
fn test5() {
count("????.######..#####", vec![1, 6, 5], 4);
}
#[test]
fn test6() {
count("?###????????", vec![3, 2, 1], 10);
}
fn unfold(cs: &str, mut runs: Vec<u8>, expected: usize) {
let mut cs = cs.chars().collect::<Vec<_>>();
unfold_line(&mut cs, &mut runs);
assert_eq!(
count_arrangements(
CountInputs {
cs: &cs,
assuming_first_char: None,
in_run: false,
runs,
},
&mut HashMap::new()
),
expected
);
}
#[test]
fn unfolded() {
unfold("???.###", vec![1, 1, 3], 1);
unfold(".??..??...?##.", vec![1, 1, 3], 16384);
unfold("?#?#?#?#?#?#?#?", vec![1, 3, 1, 6], 1);
unfold("????.#...#...", vec![4, 1, 1], 16);
unfold("????.######..#####.", vec![1, 6, 5], 2500);
unfold("?###????????", vec![3, 2, 1], 506250);
}
}

View File

@ -12,7 +12,8 @@ mod day08;
mod day09;
mod day10;
mod day11;
mod day12;
fn main() {
day11::run();
day12::run();
}