Skip to content

Commit 3904bcf

Browse files
committed
2024 day 24
1 parent 067b67d commit 3904bcf

File tree

2 files changed

+338
-0
lines changed

2 files changed

+338
-0
lines changed

crates/year2024/src/day24.rs

Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
use std::collections::HashMap;
2+
use std::ops::ControlFlow;
3+
use utils::prelude::*;
4+
5+
/// Finding swapped logic gates in an adder circuit.
6+
#[derive(Clone, Debug)]
7+
pub struct Day24 {
8+
wires: Vec<Wire>,
9+
wire_names: Vec<[u8; 3]>,
10+
x_initial: u64,
11+
y_initial: u64,
12+
z_indexes: Vec<usize>,
13+
}
14+
15+
#[derive(Copy, Clone, Debug, PartialEq)]
16+
enum Wire {
17+
X(usize),
18+
Y(usize),
19+
And(usize, usize),
20+
Or(usize, usize),
21+
Xor(usize, usize),
22+
}
23+
24+
impl Day24 {
25+
pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
26+
let Some((initial_str, gate_str)) = input.split_once("\n\n") else {
27+
return Err(InputError::new(input, 0, "expected inputs and gates"));
28+
};
29+
30+
let mut wires = Vec::new();
31+
let mut wire_names = Vec::new();
32+
let mut indexes = HashMap::new();
33+
let mut x_initial = 0;
34+
let mut y_initial = 0;
35+
let mut input_bits = 64;
36+
37+
let mut next = (b'x', 0);
38+
for item in parser::byte_range(b'x'..=b'y')
39+
.then(parser::byte_range(b'0'..=b'9'))
40+
.then(parser::byte_range(b'0'..=b'9'))
41+
.with_suffix(": ")
42+
.then(parser::byte_range(b'0'..=b'1'))
43+
.with_suffix(parser::eol())
44+
.parse_iterator(initial_str)
45+
{
46+
let (wire, b) = item?;
47+
let n = ((wire.1 - b'0') * 10 + (wire.2 - b'0')) as usize;
48+
49+
if (wire.0, n) != next {
50+
if next.0 == b'x' && wire == (b'y', b'0', b'0') {
51+
input_bits = next.1;
52+
} else {
53+
return Err(InputError::new(input, 0, "unexpected initial value"));
54+
}
55+
}
56+
57+
if wire.0 == b'x' {
58+
x_initial |= u64::from(b == b'1') << n;
59+
wires.push(Wire::X(n));
60+
} else {
61+
y_initial |= u64::from(b == b'1') << n;
62+
wires.push(Wire::Y(n));
63+
}
64+
wire_names.push(wire.into());
65+
indexes.insert(wire.into(), wires.len() - 1);
66+
67+
if n == input_bits - 1 {
68+
next = (b'?', 0);
69+
} else {
70+
next = (wire.0, n + 1);
71+
}
72+
}
73+
74+
let mut z_indexes = vec![usize::MAX; input_bits + 1];
75+
let wire = parser::byte().repeat_n::<3, _>(parser::noop());
76+
for item in wire
77+
.then(parser::literal_map!(
78+
" AND " => Wire::And as fn(usize, usize) -> Wire,
79+
" OR " => Wire::Or,
80+
" XOR " => Wire::Xor,
81+
))
82+
.then(wire.with_suffix(" -> "))
83+
.then(wire)
84+
.with_suffix(parser::eol())
85+
.parse_iterator(gate_str)
86+
{
87+
let (in1, gate, in2, out) = item?;
88+
89+
let mut index_of = |n| {
90+
*indexes.entry(n).or_insert_with(|| {
91+
wires.push(Wire::X(usize::MAX)); // Placeholder
92+
wire_names.push(n);
93+
wires.len() - 1
94+
})
95+
};
96+
97+
let in1_index = index_of(in1);
98+
let in2_index = index_of(in2);
99+
let out_index = index_of(out);
100+
101+
if wires[out_index] != Wire::X(usize::MAX) {
102+
return Err(InputError::new(input, 0, "duplicate wire definition"));
103+
}
104+
if out[0] == b'z' {
105+
let index = ((out[1] - b'0') * 10 + (out[2] - b'0')) as usize;
106+
if index < z_indexes.len() {
107+
z_indexes[index] = out_index;
108+
} else {
109+
return Err(InputError::new(input, 0, "too many z outputs"));
110+
}
111+
}
112+
113+
wires[out_index] = gate(in1_index, in2_index);
114+
}
115+
116+
if wires.contains(&Wire::X(usize::MAX)) {
117+
return Err(InputError::new(input, 0, "undefined wire"));
118+
}
119+
if z_indexes.contains(&usize::MAX) {
120+
return Err(InputError::new(input, 0, "undefined z output"));
121+
}
122+
123+
Ok(Self {
124+
wires,
125+
wire_names,
126+
x_initial,
127+
y_initial,
128+
z_indexes,
129+
})
130+
}
131+
132+
#[must_use]
133+
pub fn part1(&self) -> u64 {
134+
let mut z = 0;
135+
let mut cache = vec![None; self.wires.len()];
136+
for (i, &index) in self.z_indexes.iter().enumerate() {
137+
z |= u64::from(Self::evaluate(
138+
index,
139+
&self.wires,
140+
self.x_initial,
141+
self.y_initial,
142+
&mut cache,
143+
)) << i;
144+
}
145+
z
146+
}
147+
148+
fn evaluate(index: usize, wires: &[Wire], x: u64, y: u64, cache: &mut [Option<bool>]) -> bool {
149+
if let Some(c) = cache[index] {
150+
return c;
151+
}
152+
let v = match wires[index] {
153+
Wire::X(n) => return x & (1 << n) != 0,
154+
Wire::Y(n) => return y & (1 << n) != 0,
155+
Wire::And(a, b) => {
156+
Self::evaluate(a, wires, x, y, cache) && Self::evaluate(b, wires, x, y, cache)
157+
}
158+
Wire::Or(a, b) => {
159+
Self::evaluate(a, wires, x, y, cache) || Self::evaluate(b, wires, x, y, cache)
160+
}
161+
Wire::Xor(a, b) => {
162+
Self::evaluate(a, wires, x, y, cache) ^ Self::evaluate(b, wires, x, y, cache)
163+
}
164+
};
165+
cache[index] = Some(v);
166+
v
167+
}
168+
169+
#[must_use]
170+
pub fn part2(&self) -> String {
171+
let mut test_cases = Vec::new();
172+
for i in 0..self.z_indexes.len() - 1 {
173+
test_cases.push((i, 1u64 << i, 0u64));
174+
test_cases.push((i, 0u64, 1u64 << i));
175+
test_cases.push((i + 1, 1u64 << i, 1u64 << i));
176+
test_cases.push((i + 1, (1u64 << (i + 1)) - 1, 1u64));
177+
test_cases.push((i + 1, 1u64, (1u64 << (i + 1)) - 1));
178+
}
179+
180+
let mut wires = self.wires.clone();
181+
if self.find_swaps(&test_cases, &mut wires, 0).is_continue() {
182+
panic!("failed to find working combination");
183+
}
184+
185+
let mut changes = Vec::new();
186+
for (i, (&wire, &orig)) in wires.iter().zip(&self.wires).enumerate() {
187+
if wire != orig {
188+
changes.push(self.wire_names[i]);
189+
}
190+
}
191+
assert_eq!(changes.len(), 8, "found incorrect number of changes");
192+
193+
changes.sort_unstable();
194+
changes.into_iter().fold(String::new(), |mut acc, name| {
195+
if !acc.is_empty() {
196+
acc.push(',');
197+
}
198+
acc.push(name[0] as char);
199+
acc.push(name[1] as char);
200+
acc.push(name[2] as char);
201+
acc
202+
})
203+
}
204+
205+
fn find_swaps(
206+
&self,
207+
test_cases: &[(usize, u64, u64)],
208+
wires: &mut [Wire],
209+
assume: usize,
210+
) -> ControlFlow<()> {
211+
let mut cache = vec![None; wires.len()];
212+
let mut used = vec![false; wires.len()];
213+
for &(n, x, y) in test_cases {
214+
cache.fill(None);
215+
let sum = x + y;
216+
for i in 0..n {
217+
let b = Self::evaluate(self.z_indexes[i], wires, x, y, &mut cache);
218+
if ((sum >> i) & 1 != 0) != b {
219+
// Previous swap broke even earlier bit
220+
return ControlFlow::Continue(());
221+
}
222+
}
223+
224+
for i in 0..wires.len() {
225+
used[i] = cache[i].is_some();
226+
}
227+
228+
let b = Self::evaluate(self.z_indexes[n], wires, x, y, &mut cache);
229+
if ((sum >> n) & 1 != 0) == b {
230+
continue;
231+
}
232+
if n < assume {
233+
// Previous swap didn't fix the bit it was trying to fix
234+
return ControlFlow::Continue(());
235+
}
236+
237+
// Found bit with a wrong gate
238+
239+
// Gates which were used for the first time this bit
240+
let candidates1 = cache
241+
.iter()
242+
.zip(&used)
243+
.enumerate()
244+
.filter(|&(_, (&c, &u))| c.is_some() && !u)
245+
.map(|(i, _)| i)
246+
.collect::<Vec<_>>();
247+
// Gates which weren't used previously and only contain current bits
248+
let mask = (1 << (n + 1)) - 1;
249+
let candidates2 = Self::used_bits(wires)
250+
.iter()
251+
.zip(&used)
252+
.enumerate()
253+
.filter(|(_, (&b, &u))| b != 0 && b & mask == b && !u)
254+
.map(|(i, _)| i)
255+
.collect::<Vec<_>>();
256+
257+
// Try swapping each combination of candidates
258+
for &c1 in &candidates1 {
259+
for &c2 in &candidates2 {
260+
if c1 == c2 {
261+
continue;
262+
}
263+
264+
(wires[c1], wires[c2]) = (wires[c2], wires[c1]);
265+
266+
if !self.loops(wires) {
267+
if let ControlFlow::Break(()) = self.find_swaps(test_cases, wires, n + 1) {
268+
return ControlFlow::Break(());
269+
}
270+
}
271+
272+
// Failed, swap back and try next combination
273+
(wires[c1], wires[c2]) = (wires[c2], wires[c1]);
274+
}
275+
}
276+
277+
// No combinations worked, previous swap must be wrong
278+
return ControlFlow::Continue(());
279+
}
280+
281+
ControlFlow::Break(())
282+
}
283+
284+
fn used_bits(wires: &[Wire]) -> Vec<u64> {
285+
fn eval(index: usize, wires: &[Wire], used_bits: &mut [u64]) -> u64 {
286+
if used_bits[index] != 0 {
287+
return used_bits[index];
288+
}
289+
let v = match wires[index] {
290+
Wire::X(n) => return 1 << n,
291+
Wire::Y(n) => return 1 << n,
292+
Wire::And(a, b) | Wire::Or(a, b) | Wire::Xor(a, b) => {
293+
eval(a, wires, used_bits) | eval(b, wires, used_bits)
294+
}
295+
};
296+
used_bits[index] = v;
297+
v
298+
}
299+
300+
let mut used = vec![0; wires.len()];
301+
for i in 0..wires.len() {
302+
eval(i, wires, &mut used);
303+
}
304+
used
305+
}
306+
307+
fn loops(&self, wires: &[Wire]) -> bool {
308+
fn eval(index: usize, wires: &[Wire], checked: &mut [bool], depth: usize) -> bool {
309+
if checked[index] {
310+
return false;
311+
}
312+
if depth > wires.len() {
313+
return true;
314+
}
315+
match wires[index] {
316+
Wire::X(_) | Wire::Y(_) => {}
317+
Wire::And(a, b) | Wire::Or(a, b) | Wire::Xor(a, b) => {
318+
if eval(a, wires, checked, depth + 1) || eval(b, wires, checked, depth + 1) {
319+
return true;
320+
}
321+
}
322+
}
323+
checked[index] = true;
324+
false
325+
}
326+
327+
let mut checked = vec![false; wires.len()];
328+
for i in 0..wires.len() {
329+
if eval(i, wires, &mut checked, 0) {
330+
return true;
331+
}
332+
}
333+
false
334+
}
335+
}
336+
337+
examples!(Day24 -> (u64, &'static str) []);

crates/year2024/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,5 @@ utils::year!(2024 => year2024, ${
2525
21 => day21::Day21,
2626
22 => day22::Day22,
2727
23 => day23::Day23,
28+
24 => day24::Day24,
2829
});

0 commit comments

Comments
 (0)