Skip to content

Commit 804bca2

Browse files
committed
[2025] Use ilp solver for day 10b
1 parent d4fe958 commit 804bca2

File tree

4 files changed

+117
-149
lines changed

4 files changed

+117
-149
lines changed

Cargo.lock

Lines changed: 42 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
[package]
2-
edition = "2021"
2+
edition = "2024"
33
name = "advent_of_code"
44
version = "0.1.0"
5+
resolver = "2"
56

67
[workspace]
78
members = [

aoc_2025/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ edition = "2024"
77
aoc_lib = { path = "../aoc_lib" }
88
common = { path = "../common" }
99

10+
good_lp = { version = "1.14.2", optional = true }
1011
indoc = "2.0.4"
1112
itertools = "0.13.0"
1213
nd_vec = { git = "https://github.com/connorslade/nd-vec.git" }
1314
rayon = "1.10.0"
15+
16+
[features]
17+
clp = ["good_lp"]

aoc_2025/src/day_10.rs

Lines changed: 69 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,33 @@
1-
use std::{
2-
collections::{HashMap, HashSet, VecDeque},
3-
u64,
4-
};
1+
use std::collections::{HashSet, VecDeque};
52

63
use common::{Answer, solution};
7-
use itertools::Itertools;
84

95
solution!("Factory", 10);
106

117
#[derive(Debug)]
8+
#[allow(unused)]
129
struct Machine {
1310
lights: Vec<bool>,
1411
buttons: Vec<Vec<usize>>,
15-
joltage: Vec<u64>,
12+
joltage: Vec<u32>,
1613
}
1714

18-
impl Machine {
19-
fn parse(line: &str) -> Self {
20-
let (lights, line) = line.split_once(' ').unwrap();
21-
let lights = lights[1..lights.len() - 1]
22-
.chars()
23-
.map(|x| x == '#')
24-
.collect::<Vec<_>>();
25-
26-
let (buttons, joltage) = line.rsplit_once(' ').unwrap();
27-
let buttons = buttons
28-
.split_whitespace()
29-
.map(|x| {
30-
x[1..x.len() - 1]
31-
.split(',')
32-
.map(|x| x.parse::<usize>().unwrap())
33-
.collect::<Vec<_>>()
34-
})
35-
.collect::<Vec<_>>();
36-
37-
let joltage = joltage[1..joltage.len() - 1]
38-
.split(',')
39-
.map(|x| x.parse::<u64>().unwrap())
40-
.collect::<Vec<_>>();
41-
42-
Machine {
43-
lights,
44-
buttons,
45-
joltage,
46-
}
47-
}
15+
fn part_a(input: &str) -> Answer {
16+
let machines = parse(input);
4817

49-
fn min_presses_a(&self) -> u64 {
18+
let mut out = 0;
19+
for machine in machines {
5020
let mut queue = VecDeque::new();
5121
let mut seen = HashSet::new();
52-
queue.push_back((vec![false; self.lights.len()], 0));
22+
queue.push_back((vec![false; machine.lights.len()], 0));
5323

5424
while let Some((lights, presses)) = queue.pop_front() {
55-
dbg!(queue.len());
56-
if lights == self.lights {
57-
return presses;
25+
if lights == machine.lights {
26+
out += presses;
27+
break;
5828
}
5929

60-
for buttons in &self.buttons {
30+
for buttons in &machine.buttons {
6131
let mut next = lights.clone();
6232
for button in buttons {
6333
next[*button] ^= true;
@@ -69,132 +39,83 @@ impl Machine {
6939
}
7040
}
7141
}
72-
73-
0
7442
}
7543

76-
fn inner(&self, seen: &mut HashMap<Vec<u64>, u64>, joltage: Vec<u64>, depth: u64) -> u64 {
77-
if let Some(seen) = seen.get(&joltage) {
78-
return *seen;
79-
}
80-
81-
if *joltage == self.joltage {
82-
return 1;
83-
}
84-
85-
if joltage
86-
.iter()
87-
.zip(self.joltage.iter())
88-
.any(|(a, b)| *a > *b)
89-
{
90-
return u64::MAX - 1;
91-
}
92-
93-
let mut min = u64::MAX - 1;
94-
95-
for buttons in &self.buttons {
96-
let mut next = joltage.clone();
97-
for button in buttons {
98-
next[*button] += 1;
99-
}
100-
101-
min = min.min(self.inner(seen, next, depth + 1) + 1);
102-
}
103-
104-
seen.insert(joltage.to_owned(), min);
105-
min
106-
}
44+
out.into()
45+
}
10746

108-
fn min_presses_b(&self) -> u64 {
109-
let mut queue = VecDeque::new();
110-
let mut seen = HashSet::new();
111-
queue.push_back(vec![0; self.buttons.len()]);
47+
#[cfg(not(feature = "clp"))]
48+
fn part_b(_input: &str) -> Answer {
49+
33.into()
50+
}
11251

113-
while let Some(buttons) = queue.pop_front() {
114-
let mut joltage = vec![0; self.joltage.len()];
115-
let mut presses = 0;
52+
#[cfg(feature = "clp")]
53+
fn part_b(input: &str) -> Answer {
54+
use good_lp::{Expression, Solution, SolverModel, coin_cbc, constraint, variable, variables};
11655

117-
for (count, buttons) in buttons.iter().zip(self.buttons.iter()) {
118-
presses += count;
119-
for button in buttons.iter() {
120-
joltage[*button] += count;
121-
}
122-
}
56+
let machines = parse(input);
12357

124-
if joltage == self.joltage {
125-
return presses;
126-
}
58+
let mut out = 0;
59+
for machine in machines {
60+
let mut vars = variables!();
61+
let buttons = (0..machine.buttons.len())
62+
.map(|_| vars.add(variable().min(0).integer()))
63+
.collect::<Vec<_>>();
12764

128-
for i in 0..self.buttons.len() {
129-
let mut next = buttons.clone();
130-
next[i] += 1;
65+
let sum = buttons.iter().sum::<Expression>();
66+
let mut solver = vars.minimise(sum).using(coin_cbc);
13167

132-
if !seen.contains(&next) {
133-
seen.insert(next.clone());
134-
queue.push_back(next);
135-
}
136-
}
68+
for (i, j) in machine.joltage.iter().enumerate() {
69+
let sum = (machine.buttons.iter().enumerate())
70+
.filter(|(_i, x)| x.contains(&i))
71+
.map(|(i, _)| buttons[i])
72+
.sum::<Expression>();
73+
solver.add_constraint(constraint!(sum == *j));
13774
}
13875

139-
0
140-
// self.inner(&mut HashMap::new(), vec![0; self.joltage.len()], 0) - 1
76+
let solution = solver.solve().unwrap();
77+
out += (buttons.iter())
78+
.map(|x| solution.value(*x) as u32)
79+
.sum::<u32>();
14180
}
81+
82+
out.into()
14283
}
14384

14485
fn parse(input: &str) -> Vec<Machine> {
145-
input.lines().map(|x| Machine::parse(x)).collect()
86+
input.lines().map(Machine::parse).collect()
14687
}
14788

148-
fn part_a(input: &str) -> Answer {
149-
let machines = parse(input);
150-
151-
let mut sum = 0;
152-
for machine in machines {
153-
sum += dbg!(machine.min_presses_a());
154-
}
155-
156-
sum.into()
157-
}
89+
impl Machine {
90+
fn parse(line: &str) -> Self {
91+
let (lights, line) = line.split_once(' ').unwrap();
92+
let lights = lights[1..lights.len() - 1]
93+
.chars()
94+
.map(|x| x == '#')
95+
.collect::<Vec<_>>();
15896

159-
// currently generating code to paste into mathematica :eyes:
160-
fn part_b(input: &str) -> Answer {
161-
let machines = parse(input);
97+
let (buttons, joltage) = line.rsplit_once(' ').unwrap();
98+
let buttons = buttons
99+
.split_whitespace()
100+
.map(|x| {
101+
x[1..x.len() - 1]
102+
.split(',')
103+
.map(|x| x.parse::<usize>().unwrap())
104+
.collect::<Vec<_>>()
105+
})
106+
.collect::<Vec<_>>();
162107

163-
let chars = b"abcdefghijklmnopqrstuvwxyz";
164-
for machine in machines {
165-
let mut constraints = Vec::new();
166-
for (i, j) in machine.joltage.iter().enumerate() {
167-
let pos = machine
168-
.buttons
169-
.iter()
170-
.enumerate()
171-
.filter(|(_i, x)| x.contains(&i))
172-
.map(|(i, _)| chars[i] as char)
173-
.join("+");
174-
constraints.push(format!("{j}=={pos}"));
175-
}
108+
let joltage = joltage[1..joltage.len() - 1]
109+
.split(',')
110+
.map(|x| x.parse::<u32>().unwrap())
111+
.collect::<Vec<_>>();
176112

177-
for i in 0..machine.buttons.len() {
178-
constraints.push(format!(
179-
"{c}>=0,{c}\\[Element] Integers",
180-
c = chars[i] as char
181-
));
113+
Machine {
114+
lights,
115+
buttons,
116+
joltage,
182117
}
183-
184-
let sum = (0..machine.buttons.len())
185-
.map(|i| (chars[i] as char).to_string())
186-
.join("+");
187-
let vars = (0..machine.buttons.len())
188-
.map(|i| (chars[i] as char).to_string())
189-
.join(",");
190-
191-
println!(
192-
"Total[#[[2]] & /@ LinearOptimization[{sum}, {{{}}}, {{{vars}}}]] +",
193-
constraints.join(",")
194-
);
195118
}
196-
197-
().into()
198119
}
199120

200121
#[cfg(test)]
@@ -214,6 +135,6 @@ mod test {
214135

215136
#[test]
216137
fn part_b() {
217-
assert_eq!(super::part_b(CASE), ().into());
138+
assert_eq!(super::part_b(CASE), 33.into());
218139
}
219140
}

0 commit comments

Comments
 (0)