year2024/
day24.rs

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