aoc_2025/src/bin/day10.rs

188 lines
5.6 KiB
Rust
Raw Normal View History

2025-12-11 15:03:53 +01:00
use std::{str::FromStr, time::Instant};
use aoc_2025::{load, print_res};
use arrayvec::ArrayVec;
use bstr::BString;
use good_lp::{ProblemVariables, Solution, SolverModel};
#[derive(Debug)]
pub struct Machine {
pattern: Vec<bool>,
buttons: Vec<Vec<usize>>,
joltage: ArrayVec<u16, 16>,
}
type Parsed = Vec<Machine>;
#[inline(never)]
pub fn parsing(input: &BString) -> color_eyre::Result<Parsed> {
str::from_utf8(input)?
.lines()
.map(|line| {
let mut parts = line.split_whitespace();
let Some(pattern) = parts.next() else {
color_eyre::eyre::bail!("Empty line supplied");
};
let pattern = pattern.as_bytes();
color_eyre::eyre::ensure!(pattern.len() >= 3);
color_eyre::eyre::ensure!(*pattern.first().unwrap() == b'[');
color_eyre::eyre::ensure!(*pattern.last().unwrap() == b']');
let pattern = pattern
.iter()
.skip(1)
.take(pattern.len() - 2)
.map(|&c| match c {
b'.' => Ok(false),
b'#' => Ok(true),
_ => color_eyre::eyre::bail!("Invalid pattern entry: {}", c as char),
})
.collect::<Result<Vec<_>, _>>()?;
let mut buttons = Vec::new();
loop {
let Some(group) = parts.next() else {
color_eyre::eyre::bail!("Unexpected last group for `{line}`")
};
color_eyre::eyre::ensure!(group.len() >= 3);
fn group_content<T, I>(group: &str) -> color_eyre::Result<I>
where
T: FromStr,
T::Err: std::error::Error + Send + Sync + 'static,
I: FromIterator<T>,
{
Ok(group[1..group.len() - 1]
.split(',')
.map(str::parse)
.collect::<Result<_, _>>()?)
}
if group.as_bytes()[0] == b'{' {
color_eyre::eyre::ensure!(*group.as_bytes().last().unwrap() == b'}');
color_eyre::eyre::ensure!(parts.next().is_none());
break Ok(Machine {
pattern,
buttons,
joltage: group_content(group)?,
});
}
color_eyre::eyre::ensure!(*group.as_bytes().first().unwrap() == b'(');
color_eyre::eyre::ensure!(*group.as_bytes().last().unwrap() == b')');
buttons.push(group_content(group)?);
}
})
.collect()
}
impl Machine {
fn start_press_count(&self) -> usize {
assert!(self.pattern.len() <= 16);
let target = self.pattern.iter().rev().fold(0, |c, &b| c << 1 | b as u16);
let buttons: Vec<_> = self
.buttons
.iter()
.map(|b| b.iter().fold(0, |c, i| c | 1 << i))
.collect();
let mut seen = vec![false; 1 << self.pattern.len()];
seen[0] = true;
let mut patterns = vec![0u16];
for count in 1.. {
let mut new_patterns = Vec::new();
for pattern in patterns {
for button in &buttons {
let new = pattern ^ button;
if new == target {
return count;
} else if !seen[new as usize] {
seen[new as usize] = true;
new_patterns.push(new);
}
}
}
patterns = new_patterns;
}
unreachable!()
}
fn joltage_press_count(&self) -> usize {
let mut problem = ProblemVariables::new();
let mut presses = good_lp::Expression::with_capacity(self.buttons.len());
let mut joltage =
vec![good_lp::Expression::with_capacity(self.buttons.len()); self.joltage.len()];
for button in &self.buttons {
let var = good_lp::variable()
.integer()
.min(0)
.name(format!("{button:?}"));
let press = problem.add(var);
presses += press;
for &slot in button {
joltage[slot] += press;
}
}
let constraints = joltage
.into_iter()
.zip(&self.joltage)
.map(|(j, &t)| j.eq(t));
let mut model = problem
.minimise(&presses)
.using(good_lp::default_solver)
.with_all(constraints);
model.set_parameter("loglevel", "0");
let solution = model.solve().unwrap();
let total_presses = solution.eval(presses);
total_presses as usize
}
}
#[inline(never)]
pub fn part1(input: Parsed) {
let total: usize = input.iter().map(Machine::start_press_count).sum();
print_res!("Total presses to start: {total}");
}
#[inline(never)]
pub fn part2(input: Parsed) {
let total: usize = input.iter().map(Machine::joltage_press_count).sum();
print_res!("Total presses to set joltage: {total}");
}
pub fn main() -> color_eyre::Result<()> {
let context = load()?;
let start = Instant::now();
let parsed = parsing(&context.input)?;
let elapsed = humantime::format_duration(start.elapsed());
let start = Instant::now();
if context.part == 1 {
part1(parsed);
} else {
part2(parsed);
}
let elapsed_part = humantime::format_duration(start.elapsed());
println!(" Parsing: {elapsed}");
println!(" Solving: {elapsed_part}");
Ok(())
}