Skip to content

Commit 86f6f2f

Browse files
committed
feat: 202510
1 parent ffce405 commit 86f6f2f

3 files changed

Lines changed: 374 additions & 3 deletions

File tree

2025/10.rs

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
use advent::prelude::*;
2+
use z3::Optimize;
3+
use z3::ast::Int;
4+
5+
type Input = Vec<MachineInfo>;
6+
7+
#[derive(Debug, Clone)]
8+
struct MachineInfo {
9+
lights: Vec<bool>,
10+
buttons: Vec<Vec<usize>>,
11+
joltages: Vec<usize>,
12+
}
13+
14+
fn parse_input(input: &str) -> Input {
15+
input.lines().map(parse_machine).collect()
16+
}
17+
18+
fn parse_machine(line: &str) -> MachineInfo {
19+
let words: Vec<_> = line.split_whitespace().collect();
20+
21+
let lights = words[0]
22+
.trim_matches(|c| c == '[' || c == ']')
23+
.chars()
24+
.map(|c| c == '#')
25+
.collect();
26+
27+
let buttons = words[1..words.len() - 1]
28+
.iter()
29+
.map(|w| {
30+
w.trim_matches(|c| c == '(' || c == ')')
31+
.split(',')
32+
.map(|num| num.parse::<usize>().unwrap())
33+
.collect::<Vec<usize>>()
34+
})
35+
.collect();
36+
37+
let joltages = words[words.len() - 1]
38+
.trim_matches(|c| c == '{' || c == '}')
39+
.split(',')
40+
.map(|num| num.parse::<usize>().unwrap())
41+
.collect();
42+
43+
MachineInfo {
44+
lights,
45+
buttons,
46+
joltages,
47+
}
48+
}
49+
50+
fn default_input() -> Input {
51+
#[cfg(feature = "default-inputs")]
52+
return parse_input(include_input!(2025 / 10));
53+
#[cfg(not(feature = "default-inputs"))]
54+
panic!("default-inputs feature not enabled");
55+
}
56+
57+
fn part1(input: Input) -> usize {
58+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
59+
struct QueueState {
60+
light_states: Vec<bool>,
61+
steps: usize,
62+
}
63+
64+
input
65+
.iter()
66+
.map(|machine| {
67+
let mut queue = VecDeque::new();
68+
queue.push_back(QueueState {
69+
light_states: vec![false; machine.lights.len()],
70+
steps: 0,
71+
});
72+
73+
let mut seen_states = HashSet::new();
74+
75+
while let Some(state) = queue.pop_front() {
76+
if seen_states.contains(&state.light_states) {
77+
continue;
78+
}
79+
if machine.lights == state.light_states {
80+
return state.steps;
81+
}
82+
83+
seen_states.insert(state.light_states.clone());
84+
85+
for button in &machine.buttons {
86+
let mut new_lights = state.light_states.clone();
87+
for &idx in button {
88+
new_lights[idx] = !new_lights[idx];
89+
}
90+
queue.push_back(QueueState {
91+
light_states: new_lights,
92+
steps: state.steps + 1,
93+
});
94+
}
95+
}
96+
97+
panic!("no solution found for machine {:?}", machine);
98+
})
99+
.sum()
100+
}
101+
102+
fn part2(input: Input) -> i64 {
103+
input
104+
.iter()
105+
.map(|machine| {
106+
let mut buttons = vec![];
107+
for (idx, _button) in machine.buttons.iter().enumerate() {
108+
buttons.push(Int::fresh_const(&format!("btn_{idx}")))
109+
}
110+
111+
let solver = Optimize::new();
112+
for btn in &buttons {
113+
solver.assert(&btn.ge(0));
114+
}
115+
116+
for (joltage_idx, &joltage) in machine.joltages.iter().enumerate() {
117+
let mut sum = Int::from_i64(0);
118+
for (btn_idx, button) in machine.buttons.iter().enumerate() {
119+
if button.contains(&joltage_idx) {
120+
sum += &buttons[btn_idx];
121+
}
122+
}
123+
solver.assert(&sum.eq(joltage as i64));
124+
}
125+
126+
solver.minimize(&buttons.iter().fold(Int::from_i64(0), |acc, b| acc + b));
127+
128+
solver.check(&[]);
129+
let model = solver.get_model().unwrap();
130+
131+
let vals: Vec<i64> = buttons
132+
.iter()
133+
.map(|b| model.eval(b, true).unwrap().as_i64().unwrap())
134+
.collect();
135+
vals.iter().sum::<i64>()
136+
})
137+
.sum()
138+
}
139+
140+
fn main() {
141+
let solution = advent::new(default_input).part(part1).part(part2).build();
142+
solution.cli()
143+
}
144+
145+
#[cfg(test)]
146+
mod tests {
147+
use super::*;
148+
149+
#[ignore]
150+
#[test]
151+
fn default() {
152+
let input = default_input();
153+
assert_eq!(part1(input.clone()), 473);
154+
assert_eq!(part2(input), 18681);
155+
}
156+
157+
#[test]
158+
fn examples() {
159+
let input = parse_input(
160+
"[.##.] (3) (1,3) (2) (2,3) (0,2) (0,1) {3,5,4,7}
161+
[...#.] (0,2,3,4) (2,3) (0,4) (0,1,2) (1,2,3,4) {7,5,12,7,2}
162+
[.###.#] (0,1,2,3,4) (0,3,4) (0,1,2,4,5) (1,2) {10,11,11,5,10,5}",
163+
);
164+
assert_eq!(part1(input.clone()), 7);
165+
assert_eq!(part2(input), 33);
166+
}
167+
}

0 commit comments

Comments
 (0)