Day 10
This commit is contained in:
parent
c9bd84ebdb
commit
2b436350f6
4 changed files with 233 additions and 0 deletions
42
Cargo.lock
generated
42
Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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
187
src/bin/day10.rs
Normal 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(())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue