Solve day 12 part 2
This commit is contained in:
parent
d194e892af
commit
d04d193f4c
3 changed files with 242 additions and 47 deletions
108
src/bin/12.rs
108
src/bin/12.rs
|
@ -1,12 +1,14 @@
|
|||
use aoc2023::*;
|
||||
use std::collections::VecDeque;
|
||||
use cached::proc_macro::cached;
|
||||
use itertools::Itertools;
|
||||
use std::{collections::VecDeque, fmt::Debug, iter};
|
||||
|
||||
const INPUT: &str = include_str!("../../input/12");
|
||||
|
||||
fn main() {
|
||||
assert_example!(part1, "12-test", 21);
|
||||
println!("Part 1: {}", part1(INPUT));
|
||||
assert_example!(part2, "12-test", 0);
|
||||
assert_example!(part2, "12-test", 525152);
|
||||
println!("Part 2: {}", part2(INPUT));
|
||||
}
|
||||
|
||||
|
@ -14,15 +16,20 @@ fn part1(input: &str) -> usize {
|
|||
input
|
||||
.lines()
|
||||
.map(Row::parse)
|
||||
.map(Row::possible_arrangements)
|
||||
.map(possible_arrangements)
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn part2(input: &str) -> usize {
|
||||
0
|
||||
input
|
||||
.lines()
|
||||
.map(Row::parse)
|
||||
.map(Row::unfold)
|
||||
.map(possible_arrangements)
|
||||
.sum()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
struct Row {
|
||||
springs: VecDeque<Spring>,
|
||||
groups: VecDeque<usize>,
|
||||
|
@ -36,51 +43,60 @@ impl Row {
|
|||
Self { springs, groups }
|
||||
}
|
||||
|
||||
fn possible_arrangements(mut self) -> usize {
|
||||
// Pop all operational springs on the front and then get the first different one.
|
||||
let first = loop {
|
||||
match self.springs.pop_front() {
|
||||
None => return if self.groups.is_empty() { 1 } else { 0 },
|
||||
Some(Spring::Operational) => continue,
|
||||
Some(Spring::Broken) => break InterestingSpring::Broken,
|
||||
Some(Spring::Unknown) => break InterestingSpring::Unknown,
|
||||
}
|
||||
};
|
||||
fn unfold(self) -> Self {
|
||||
let springs = iter::repeat(self.springs).take(5);
|
||||
let springs = Itertools::intersperse(springs, [Spring::Unknown].into())
|
||||
.flatten()
|
||||
.collect();
|
||||
let groups = iter::repeat(self.groups).take(5).flatten().collect();
|
||||
Self { springs, groups }
|
||||
}
|
||||
}
|
||||
|
||||
match first {
|
||||
InterestingSpring::Broken => {
|
||||
// A broken spring requires a run that matches the group.
|
||||
let Some(mut group) = self.groups.pop_front() else {
|
||||
// There was a broken spring but no group
|
||||
return 0;
|
||||
};
|
||||
#[cached]
|
||||
fn possible_arrangements(mut row: Row) -> usize {
|
||||
// Pop all operational springs on the front and then get the first different one.
|
||||
let first = loop {
|
||||
match row.springs.pop_front() {
|
||||
None => return if row.groups.is_empty() { 1 } else { 0 },
|
||||
Some(Spring::Operational) => continue,
|
||||
Some(Spring::Broken) => break InterestingSpring::Broken,
|
||||
Some(Spring::Unknown) => break InterestingSpring::Unknown,
|
||||
}
|
||||
};
|
||||
|
||||
// We already encountered the first broken spring.
|
||||
group -= 1;
|
||||
match first {
|
||||
InterestingSpring::Broken => {
|
||||
// A broken spring requires a run that matches the group.
|
||||
let Some(mut group) = row.groups.pop_front() else {
|
||||
// There was a broken spring but no group
|
||||
return 0;
|
||||
};
|
||||
|
||||
// Pop all required springs or return if impossible.
|
||||
loop {
|
||||
match self.springs.pop_front() {
|
||||
None if group > 0 => return 0,
|
||||
None if group == 0 => break,
|
||||
Some(Spring::Broken | Spring::Unknown) if group > 0 => group -= 1,
|
||||
Some(Spring::Broken) if group == 0 => return 0,
|
||||
Some(Spring::Operational) if group > 0 => return 0,
|
||||
Some(Spring::Operational | Spring::Unknown) => break,
|
||||
other => panic!("group detection weird case {other:?}, group={group}"),
|
||||
}
|
||||
// We already encountered the first broken spring.
|
||||
group -= 1;
|
||||
|
||||
// Pop all required springs or return if impossible.
|
||||
loop {
|
||||
match row.springs.pop_front() {
|
||||
None if group > 0 => return 0,
|
||||
None => break,
|
||||
Some(Spring::Broken | Spring::Unknown) if group > 0 => group -= 1,
|
||||
Some(Spring::Broken) => return 0,
|
||||
Some(Spring::Operational) if group > 0 => return 0,
|
||||
Some(Spring::Operational | Spring::Unknown) => break,
|
||||
}
|
||||
}
|
||||
|
||||
self.possible_arrangements()
|
||||
}
|
||||
InterestingSpring::Unknown => {
|
||||
// We don't know what kind of spring is here, recursively try both options.
|
||||
let mut a = self.clone();
|
||||
let mut b = self;
|
||||
a.springs.push_front(Spring::Broken);
|
||||
b.springs.push_front(Spring::Operational);
|
||||
a.possible_arrangements() + b.possible_arrangements()
|
||||
}
|
||||
possible_arrangements(row)
|
||||
}
|
||||
InterestingSpring::Unknown => {
|
||||
// We don't know what kind of spring is here, recursively try both options.
|
||||
let mut a = row.clone();
|
||||
let mut b = row;
|
||||
a.springs.push_front(Spring::Broken);
|
||||
b.springs.push_front(Spring::Operational);
|
||||
possible_arrangements(a) + possible_arrangements(b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -91,7 +107,7 @@ enum InterestingSpring {
|
|||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
enum Spring {
|
||||
Broken,
|
||||
Unknown,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue