1
0
Fork 0
aoc2023/src/bin/12.rs
2023-12-19 23:53:54 +01:00

126 lines
3.6 KiB
Rust

use aoc2023::*;
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", 525152);
println!("Part 2: {}", part2(INPUT));
}
fn part1(input: &str) -> usize {
input
.lines()
.map(Row::parse)
.map(possible_arrangements)
.sum()
}
fn part2(input: &str) -> usize {
input
.lines()
.map(Row::parse)
.map(Row::unfold)
.map(possible_arrangements)
.sum()
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
struct Row {
springs: VecDeque<Spring>,
groups: VecDeque<usize>,
}
impl Row {
fn parse(line: &str) -> Self {
let (springs, groups) = line.split_once(' ').unwrap();
let springs = springs.chars().map(Spring::from).collect();
let groups = groups.split(',').map(|n| n.parse().unwrap()).collect();
Self { springs, groups }
}
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 }
}
}
#[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,
}
};
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;
};
// 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,
}
}
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)
}
}
}
#[derive(Copy, Clone, Debug)]
enum InterestingSpring {
Broken,
Unknown,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
enum Spring {
Broken,
Unknown,
Operational,
}
impl From<char> for Spring {
fn from(value: char) -> Self {
match value {
'#' => Self::Broken,
'.' => Self::Operational,
'?' => Self::Unknown,
other => panic!("unknown spring '{other}'"),
}
}
}