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",
|
"bstr",
|
||||||
"clap",
|
"clap",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
|
"good_lp",
|
||||||
"humantime",
|
"humantime",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
]
|
]
|
||||||
|
|
@ -157,6 +158,25 @@ version = "0.7.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
|
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]]
|
[[package]]
|
||||||
name = "color-eyre"
|
name = "color-eyre"
|
||||||
version = "0.6.5"
|
version = "0.6.5"
|
||||||
|
|
@ -200,12 +220,28 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fnv"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gimli"
|
name = "gimli"
|
||||||
version = "0.32.3"
|
version = "0.32.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
|
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]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
|
@ -290,6 +326,12 @@ version = "0.2.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pkg-config"
|
||||||
|
version = "0.3.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.103"
|
version = "1.0.103"
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ arrayvec = "0.7.6"
|
||||||
bstr = "1.11.0"
|
bstr = "1.11.0"
|
||||||
clap = { version = "4.5.21", features = ["derive"] }
|
clap = { version = "4.5.21", features = ["derive"] }
|
||||||
color-eyre = "0.6.3"
|
color-eyre = "0.6.3"
|
||||||
|
good_lp = "1.14.2"
|
||||||
humantime = "2.1.0"
|
humantime = "2.1.0"
|
||||||
rustc-hash = "2.1.1"
|
rustc-hash = "2.1.1"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,10 +39,13 @@
|
||||||
hyperfine
|
hyperfine
|
||||||
cargo-flamegraph
|
cargo-flamegraph
|
||||||
cargo-show-asm
|
cargo-show-asm
|
||||||
|
cbc
|
||||||
|
pkg-config
|
||||||
]);
|
]);
|
||||||
RUST_PATH = "${rust}";
|
RUST_PATH = "${rust}";
|
||||||
RUST_DOC_PATH = "${rust}/share/doc/rust/html/std/index.html";
|
RUST_DOC_PATH = "${rust}/share/doc/rust/html/std/index.html";
|
||||||
AOC_YEAR = "2025";
|
AOC_YEAR = "2025";
|
||||||
|
LD_LIBRARY_PATH="${pkgs.cbc}/lib";
|
||||||
};
|
};
|
||||||
|
|
||||||
defaultPackage = craneLib.buildPackage {
|
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