This commit is contained in:
traxys 2025-12-11 15:03:53 +01:00
parent c9bd84ebdb
commit 2b436350f6
4 changed files with 233 additions and 0 deletions

42
Cargo.lock generated
View file

@ -75,6 +75,7 @@ dependencies = [
"bstr",
"clap",
"color-eyre",
"good_lp",
"humantime",
"rustc-hash",
]
@ -157,6 +158,25 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
[[package]]
name = "coin_cbc"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d602045cd2e7ad02608a71492af94357f493a6f3c934ce854c03bf10fddc5780"
dependencies = [
"coin_cbc_sys",
"lazy_static",
]
[[package]]
name = "coin_cbc_sys"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "085619f8bdc38e24e25c6336ecc3f2e6c0543d67566dff6daef0e32f7ac20f76"
dependencies = [
"pkg-config",
]
[[package]]
name = "color-eyre"
version = "0.6.5"
@ -200,12 +220,28 @@ dependencies = [
"once_cell",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "gimli"
version = "0.32.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
[[package]]
name = "good_lp"
version = "1.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "776aa1ba88ac058e78408c17f4dbff826a51ae08ed6642f71ca0edd7fe9383f3"
dependencies = [
"coin_cbc",
"fnv",
]
[[package]]
name = "heck"
version = "0.5.0"
@ -290,6 +326,12 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "proc-macro2"
version = "1.0.103"

View file

@ -9,6 +9,7 @@ arrayvec = "0.7.6"
bstr = "1.11.0"
clap = { version = "4.5.21", features = ["derive"] }
color-eyre = "0.6.3"
good_lp = "1.14.2"
humantime = "2.1.0"
rustc-hash = "2.1.1"

View file

@ -39,10 +39,13 @@
hyperfine
cargo-flamegraph
cargo-show-asm
cbc
pkg-config
]);
RUST_PATH = "${rust}";
RUST_DOC_PATH = "${rust}/share/doc/rust/html/std/index.html";
AOC_YEAR = "2025";
LD_LIBRARY_PATH="${pkgs.cbc}/lib";
};
defaultPackage = craneLib.buildPackage {

187
src/bin/day10.rs Normal file
View file

@ -0,0 +1,187 @@
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(())
}