diff --git a/Cargo.lock b/Cargo.lock index d65d008..36031a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -170,6 +170,26 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bindgen" +version = "0.71.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + [[package]] name = "bitflags" version = "2.6.0" @@ -190,13 +210,23 @@ checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "cc" -version = "1.2.5" +version = "1.2.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" dependencies = [ + "find-msvc-tools", "shlex", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -238,6 +268,17 @@ dependencies = [ "phf_codegen", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.5.23" @@ -278,6 +319,15 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +[[package]] +name = "cmake" +version = "0.1.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49d74c227b6cc9f3c51a2c7c667a05b6453f7f0f952a5f8e4493bb9e731d68e" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.3" @@ -348,6 +398,12 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + [[package]] name = "fnv" version = "1.0.7" @@ -445,6 +501,22 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "good_lp" +version = "1.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "776aa1ba88ac058e78408c17f4dbff826a51ae08ed6642f71ca0edd7fe9383f3" +dependencies = [ + "fnv", + "highs", +] + [[package]] name = "h2" version = "0.4.7" @@ -476,6 +548,26 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "highs" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9f739d4b9d063219b2dfa00705d8f2127fcead4d053867a5557c953dc3a4b99" +dependencies = [ + "highs-sys", + "log", +] + +[[package]] +name = "highs-sys" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c51d1e4e682c01dd882d2d0cd89b6725cd5805dd307d290aac0a37be6fe0c58" +dependencies = [ + "bindgen", + "cmake", +] + [[package]] name = "http" version = "1.2.0" @@ -772,6 +864,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.14.0" @@ -803,6 +904,16 @@ version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -817,9 +928,9 @@ checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "log" -version = "0.4.22" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" @@ -833,6 +944,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.2" @@ -870,6 +987,16 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1015,6 +1142,16 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.92" @@ -1142,6 +1279,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustix" version = "0.38.42" @@ -1367,9 +1510,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.91" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -1684,6 +1827,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-registry" version = "0.2.0" @@ -1950,7 +2099,8 @@ version = "0.1.0" name = "y2025d10" version = "0.1.0" dependencies = [ - "itertools", + "good_lp", + "itertools 0.14.0", ] [[package]] diff --git a/rust/y2025/d10/Cargo.toml b/rust/y2025/d10/Cargo.toml index 10f5381..64e5cbf 100644 --- a/rust/y2025/d10/Cargo.toml +++ b/rust/y2025/d10/Cargo.toml @@ -7,4 +7,5 @@ edition = "2021" path = "y2025d10.rs" [dependencies] +good_lp = { version = "1.14.2", features = ["highs"], default-features = false } itertools = "0.14.0" diff --git a/rust/y2025/d10/y2025d10.rs b/rust/y2025/d10/y2025d10.rs index 83a9d4c..a4120f8 100644 --- a/rust/y2025/d10/y2025d10.rs +++ b/rust/y2025/d10/y2025d10.rs @@ -1,5 +1,6 @@ use std::str::FromStr; +use good_lp::{constraint, default_solver, Expression, Solution, SolverModel}; use itertools::Itertools; pub fn part1(input: String) -> Result> { @@ -15,7 +16,7 @@ pub fn part1(input: String) -> Result> { pub fn part2(input: String) -> Result> { let machines = parse_input(input)?; - let tot: usize = machines + let tot: u64 = machines .iter() .map(|m| m.find_joltage_presses()) .collect::, _>>()? @@ -30,9 +31,9 @@ fn parse_input(input: String) -> Result, Box #[derive(Debug)] struct Machine { - target: Vec, - buttons: Vec>, - _joltage: Vec, + target: u16, // binary representation gives on/off lights + buttons: Vec, // i-th binary digit is 1 iff the button affects the i-th light + joltage: Vec, } impl FromStr for Machine { @@ -44,17 +45,25 @@ impl FromStr for Machine { let target = target_str .trim_start_matches('[') .trim_end_matches(']') - .chars() - .map(|c| c == '#') - .collect(); + .char_indices() + .filter_map(|(i, c)| { + if c == '#' { + Some(2u16.pow(i as u32)) + } else { + None + } + }) + .sum(); let buttons = button_str .split(' ') - .map(|s| { - s.trim_start_matches('(') + .map(|s| -> Result { + let ids = s + .trim_start_matches('(') .trim_end_matches(')') .split(',') .map(str::parse) - .collect::, _>>() + .collect::, _>>()?; + Ok(ids.into_iter().map(|d| 2u16.pow(d)).sum()) }) .collect::, _>>()?; let joltage = joltage_str @@ -67,23 +76,19 @@ impl FromStr for Machine { Ok(Self { target, buttons, - _joltage: joltage, + joltage, }) } } impl Machine { fn find_light_presses(&self) -> Result> { + // Each button is either pressed or not + // Iterate over combinations of buttons of increasing length, until one matches the target for n in 1..=self.buttons.len() { for bs in self.buttons.iter().combinations(n) { - let s = bs - .iter() - .fold(vec![false; self.target.len()], |mut acc, b| { - for p in b.iter() { - acc[*p] = !acc[*p]; - } - acc - }); + // xor all pressed buttons + let s = bs.iter().fold(0u16, |acc, &b| acc ^ b); if s == self.target { return Ok(n); } @@ -93,7 +98,33 @@ impl Machine { Err("Solution not found".into()) } - fn find_joltage_presses(&self) -> Result> { - Err("Solution not found".into()) + fn find_joltage_presses(&self) -> Result> { + good_lp::variables! {vars: 0 <= xs[self.buttons.len()] (integer); } + + let objective = xs.iter().sum::(); + + let constraints: Vec<_> = self + .joltage + .iter() + .enumerate() + .map(|(i, jolt)| { + good_lp::constraint!( + self.buttons + .iter() + .zip(xs.iter()) + .filter_map(|(b, x)| if b & (1 << i) != 0 { Some(*x) } else { None }) + .sum::() + == *jolt + ) + }) + .collect(); + + let problem = constraints.into_iter().fold( + vars.minimise(&objective).using(default_solver), // unconstrained problem, fold to add all constraints + |p, c| p.with(c), + ); + let solution = problem.solve()?; + + Ok(solution.eval(objective) as u64) } }