187 lines
5.6 KiB
Rust
187 lines
5.6 KiB
Rust
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(())
|
|
}
|