diff --git a/2024/day24/src/lib.rs b/2024/day24/src/lib.rs index 4220c46..130e24a 100644 --- a/2024/day24/src/lib.rs +++ b/2024/day24/src/lib.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use helper::{Day, IteratorExt, Variants}; @@ -121,7 +121,263 @@ fn part1(input: &str) -> u64 { result } -fn part2(_input: &str) -> u64 { +fn part2(input: &str) -> u64 { + /* + Adding two binary numbers + + LSB: + X Y + ---|---|--- + | | + +---|--+ + (^)--+ | + | (&)-+ + | | + ---|---|--- + Z Carry + + + other bits: + X Y Carry + ---|---|---|--- + | | | + | +---|-----+ + +---|---|--+ | + | | | | | + (^)--+ | +-(&) + | | | + +---(&)-+ | + | | | | + (^)------+ | + | | | + | (|)-------+ + | ++ + ---|---|--- + Z Carry + */ + + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] + enum Wire<'a> { + X(u8), + Y(u8), + Z(u8), + Intermediate(&'a str), + } + #[derive(Debug, Clone, Copy)] + enum Value { + Unknown, + Op(Op, usize, usize), + } + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + enum Op { + And, + Or, + Xor, + } + + let mut wires = Vec::new(); + let mut wire_names = Vec::new(); + let mut wire_by_name = HashMap::new(); + + fn intern<'i>( + wires: &mut Vec, + wire_names: &mut Vec>, + wire_by_name: &mut HashMap, usize>, + name: &'i str, + ) -> (usize, Wire<'i>) { + let name = if let Some(n) = name.strip_prefix("x") { + Wire::X(n.parse().unwrap()) + } else if let Some(n) = name.strip_prefix("y") { + Wire::Y(n.parse().unwrap()) + } else if let Some(n) = name.strip_prefix("z") { + Wire::Z(n.parse().unwrap()) + } else { + Wire::Intermediate(name) + }; + let idx = *wire_by_name.entry(name).or_insert_with(|| { + let idx = wires.len(); + wires.push(Value::Unknown); + wire_names.push(name); + idx + }); + (idx, name) + } + + let (_, gates) = input.split_once("\n\n").unwrap(); + + let mut zs = Vec::new(); + + for computed in gates.lines() { + let (expr, wire) = computed.split_once(" -> ").unwrap(); + let [lhs, op, rhs] = expr.split(' ').collect_array().unwrap(); + + let (wire, wire_name) = intern(&mut wires, &mut wire_names, &mut wire_by_name, wire); + let (lhs, _) = intern(&mut wires, &mut wire_names, &mut wire_by_name, lhs); + let (rhs, _) = intern(&mut wires, &mut wire_names, &mut wire_by_name, rhs); + + if let Wire::Z(_) = wire_name { + zs.push(wire) + } + + let op = match op { + "AND" => Op::And, + "OR" => Op::Or, + "XOR" => Op::Xor, + _ => panic!("Invalid op: {op}"), + }; + + wires[wire] = Value::Op(op, lhs, rhs); + } + + zs.sort_by_key(|wire| wire_names[*wire]); + + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + enum Pattern { + Op(Op, Box, Box), + X(u8), + Y(u8), + } + + fn check_match( + wire_names: &[Wire<'_>], + wires: &[Value], + check_cache: &mut HashMap<(usize, Pattern), Result<(), (usize, Pattern)>>, + wire: usize, + pattern: &Pattern, + ) -> Result<(), (usize, Pattern)> { + let key = (wire, pattern.clone()); + if let Some(cache_value) = check_cache.get(&key) { + return cache_value.clone(); + } + let value = check_match_uncached(wire_names, wires, check_cache, wire, pattern); + let prev_cache_entry = check_cache.insert(key, value.clone()); + assert!(prev_cache_entry.is_none()); + value + } + fn check_match_uncached( + wire_names: &[Wire<'_>], + wires: &[Value], + check_cache: &mut HashMap<(usize, Pattern), Result<(), (usize, Pattern)>>, + wire: usize, + pattern: &Pattern, + ) -> Result<(), (usize, Pattern)> { + //eprintln!("{:?} matches {:?}", wire_names[wire], pattern); + + match (wire_names[wire], pattern) { + (_, Pattern::Op(_, _, _)) => {} + (Wire::X(x_value), Pattern::X(x_pattern)) if x_value == *x_pattern => return Ok(()), + (Wire::Y(y_value), Pattern::Y(y_pattern)) if y_value == *y_pattern => return Ok(()), + _ => { + return Err((wire, pattern.clone())); + } + } + + match (wires[wire], pattern) { + ( + Value::Op(op_value, lhs_wire, rhs_wire), + Pattern::Op(op_pattern, pattern1, pattern2), + ) if op_value == *op_pattern => { + let lhs_1 = check_match(wire_names, wires, check_cache, lhs_wire, &pattern1); + let rhs_2 = check_match(wire_names, wires, check_cache, rhs_wire, &pattern2); + if lhs_1.is_ok() && rhs_2.is_ok() { + return Ok(()); + } + + let lhs_2 = check_match(wire_names, wires, check_cache, lhs_wire, &pattern2); + let rhs_1 = check_match(wire_names, wires, check_cache, rhs_wire, &pattern1); + if lhs_2.is_ok() && rhs_1.is_ok() { + return Ok(()); + } + + // If both inputs are wrong, this node is likely the culprit. + // If only one input is wrong, this input is the culprit. + if lhs_1.is_ok() { + rhs_2 + } else if rhs_2.is_ok() { + lhs_1 + } else if lhs_2.is_ok() { + rhs_1 + } else if rhs_1.is_ok() { + lhs_2 + } else { + Err((wire, pattern.clone())) + } + } + _ => Err((wire, pattern.clone())), + } + } + + let mut incorrect_gates = HashSet::new(); + + let mut prev_carry_pat = Pattern::X(255); // dummy + + let mut check_cache = HashMap::new(); + + for z_wire in zs { + let Wire::Z(pos) = wire_names[z_wire] else { + unreachable!() + }; + + eprintln!("------------ checking z{pos:0>2}"); + + let input_xor_pat = Pattern::Op( + Op::Xor, + Box::new(Pattern::X(pos)), + Box::new(Pattern::Y(pos)), + ); + + let (z_pat, carry_pat) = match pos { + 0 => { + let carry_pat = Pattern::Op( + Op::And, + Box::new(Pattern::X(pos)), + Box::new(Pattern::Y(pos)), + ); + (input_xor_pat, carry_pat) + } + _ => { + let carry_pat = Pattern::Op( + Op::Or, + Box::new(Pattern::Op( + Op::And, + Box::new(Pattern::X(pos)), + Box::new(Pattern::Y(pos)), + )), + Box::new(Pattern::Op( + Op::And, + Box::new(prev_carry_pat.clone()), + Box::new(input_xor_pat.clone()), + )), + ); + ( + Pattern::Op(Op::Xor, Box::new(input_xor_pat), Box::new(prev_carry_pat)), + carry_pat, + ) + } + }; + prev_carry_pat = carry_pat; + + if let Err((incorrect_wire, pat)) = + check_match(&wire_names, &wires, &mut check_cache, z_wire, &z_pat) + { + eprintln!("error: {:?}", wire_names[incorrect_wire]); + incorrect_gates.insert((incorrect_wire, pat)); + } + } + + dbg!(incorrect_gates.len()); + + for (wire, pat) in &incorrect_gates { + eprintln!("{:?}", wire_names[*wire]); + + let pair = incorrect_gates.iter().find(|(other_wire, other_pat)| { + check_match(&wire_names, &wires, &mut check_cache, *wire, other_pat).is_ok() + }); + if let Some(pair) = pair { + eprintln!(" pairs with {:?}", wire_names[pair.0]); + } + } + 0 }