Advent of Code solutions
at main 308 lines 9.0 kB view raw
1use std::collections::{HashMap, HashSet, VecDeque}; 2 3use advent_core::{day_stuff, ex_for_day, Day}; 4 5pub struct Day24; 6 7pub type Wires = HashMap<String, bool>; 8pub type Gates = HashMap<(String, String, String), Gate>; 9 10fn find_gate<'a>(gates: &'a Gates, lhs: &str, rhs: &str, op: Op) -> Option<&'a Gate> { 11 gates.get(&(lhs.to_string(), rhs.to_string(), format!("{op:?}"))) 12} 13 14#[derive(Debug, Clone, Copy, Eq, PartialEq)] 15pub enum Op { 16 And, 17 Or, 18 Xor, 19} 20 21impl Op { 22 fn eval(&self, lhs: bool, rhs: bool) -> bool { 23 match self { 24 Self::And => lhs & rhs, 25 Self::Or => lhs | rhs, 26 Self::Xor => lhs ^ rhs, 27 } 28 } 29} 30 31#[derive(Debug, Clone)] 32pub struct Gate { 33 lhs: String, 34 op: Op, 35 rhs: String, 36 target: String, 37} 38 39impl Gate { 40 pub fn parse(raw: &str) -> Self { 41 let mut s = raw.split(" "); 42 let lhs = s.next().unwrap().to_string(); 43 let op = s.next().unwrap(); 44 let rhs = s.next().unwrap().to_string(); 45 let target = s.nth(1).unwrap().to_string(); 46 let op = match op { 47 "AND" => Op::And, 48 "OR" => Op::Or, 49 "XOR" => Op::Xor, 50 _ => panic!(), 51 }; 52 Self { 53 lhs, 54 rhs, 55 target, 56 op, 57 } 58 } 59 60 pub fn run(&self, wires: &mut Wires) -> bool { 61 if let (Some(&lhs), Some(&rhs)) = (wires.get(&self.lhs), wires.get(&self.rhs)) { 62 wires.insert(self.target.clone(), self.op.eval(lhs, rhs)); 63 true 64 } else { 65 false 66 } 67 } 68} 69 70enum AdderTestResult { 71 Okay(String), 72 SwapNeeded(String, String), 73 CompletelyWrong, 74 End, 75} 76 77impl AdderTestResult { 78 fn unwrap_okay(self) -> String { 79 match self { 80 Self::Okay(s) => s, 81 _ => panic!(), 82 } 83 } 84} 85 86// The first adder is a half adder and needs to follow this format: 87// 88// x00, y00 -> AND -> [carry output wire] 89// x00, y00 -> XOR -> z00 90// 91fn test_first_adder(gates: &Gates) -> AdderTestResult { 92 let x0 = "x00".to_string(); 93 let y0 = "y00".to_string(); 94 if let Some(Gate { target, .. }) = find_gate(gates, &x0, &y0, Op::And) { 95 if let Some(Gate { 96 target: z_target, .. 97 }) = find_gate(gates, &x0, &y0, Op::Xor) 98 { 99 if z_target == "z00" { 100 if target == "z01" { 101 AdderTestResult::End 102 } else { 103 AdderTestResult::Okay(target.to_string()) 104 } 105 } else { 106 AdderTestResult::SwapNeeded(target.to_string(), z_target.to_string()) 107 } 108 } else { 109 AdderTestResult::CompletelyWrong 110 } 111 } else { 112 AdderTestResult::CompletelyWrong 113 } 114} 115 116// Full adders must follow this pattern: 117// 118// 1. x[], y[] -> AND -> [xy_and_target] 119// 2. x[], y[] -> XOR -> [xy_xor_target] 120// 3. [carry_input], [xy_xor_target] -> XOR -> z[] 121// 4. [carry_input], [xy_xor_target] -> AND -> [carry_xy_target] 122// 5. [carry_xy_target], [xy_and_target] -> OR -> [carry_output] 123// 124// Of the gates, the ones that can swap outputs without creating a loop are as follows: 125// A. #1 & #2 126// B. #3 & #5 127// C. #3 & #4 128// D. #1 & #3 129// 130// These ones would result in insane or same output: 131// #1 & #4, this actually results in the same output no matter what 132// #2 & #3, this will result in #3's own output be its input which is invalid to this problem 133// #2 & #4, ^ 134// #2 & #5, ^ 135// #4 & #5, ^ 136// 137fn test_adder(num: usize, carry_input: &str, gates: &Gates) -> AdderTestResult { 138 let x = format!("x{num:02}"); 139 let y = format!("y{num:02}"); 140 let z = format!("z{num:02}"); 141 if let ( 142 Some(Gate { 143 target: xy_and_target, 144 .. 145 }), 146 Some(Gate { 147 target: xy_xor_target, 148 .. 149 }), 150 ) = ( 151 find_gate(gates, &x, &y, Op::And), 152 find_gate(gates, &x, &y, Op::Xor), 153 ) { 154 if let Some(Gate { 155 target: z_target, .. 156 }) = find_gate(gates, carry_input, xy_xor_target.as_str(), Op::Xor) 157 { 158 if *z_target != z { 159 // We know gate #3 is pointing to the wrong target, as it should be pointing to z[] 160 // We can now confidently swap z[] and this target 161 // This covers invalid state B, C, and D 162 return AdderTestResult::SwapNeeded(z_target.to_string(), z); 163 } 164 } else { 165 // We know that gate #2 has an invalid output, and since the only case that involves #2 166 // is case A, we know that we're swapped with gate #1, we simply need to return the two 167 // targets we already have 168 return AdderTestResult::SwapNeeded( 169 xy_and_target.to_string(), 170 xy_xor_target.to_string(), 171 ); 172 }; 173 174 // From here we've checked all test cases, we can confidently attempt to find the carry 175 // output now 176 let carry_xy_target = &find_gate(gates, xy_xor_target, carry_input, Op::And) 177 .expect("Failed to find carry_xy_target") 178 .target; 179 let carry_out = &find_gate(gates, carry_xy_target, xy_and_target, Op::Or) 180 .expect("Failed to find carry_out") 181 .target; 182 if *carry_out == format!("z{:02}", num + 1) { 183 AdderTestResult::End 184 } else { 185 AdderTestResult::Okay(carry_out.to_string()) 186 } 187 } else { 188 AdderTestResult::CompletelyWrong 189 } 190} 191 192fn swap_outputs(gates: &mut Gates, out1: &String, out2: &String) { 193 gates.values_mut().for_each(|g| { 194 if g.target == *out1 { 195 g.target = out2.to_string(); 196 } else if g.target == *out2 { 197 g.target = out1.to_string(); 198 } 199 }); 200} 201 202// 0,1, 203// z16,tdv,hnd,z09,z23,bks,nrn,tjp 204 205impl Day for Day24 { 206 day_stuff!(24, "4", "bks,hnd,nrn,tdv,tjp,z09,z16,z23", (Wires, Gates)); 207 208 fn part_1((mut wires, gates): Self::Input) -> Option<String> { 209 let mut all_zs = gates 210 .values() 211 .filter(|g| g.target.starts_with('z')) 212 .map(|g| &g.target) 213 .collect::<Vec<_>>(); 214 all_zs.sort(); 215 all_zs.dedup(); 216 217 let mut current_zs = HashSet::<&String>::with_capacity(all_zs.len()); 218 219 let mut queue = gates.values().collect::<VecDeque<_>>(); 220 221 while let Some(gate) = queue.pop_front() 222 && current_zs.len() < all_zs.len() 223 { 224 if gate.run(&mut wires) { 225 if gate.target.starts_with('z') { 226 current_zs.insert(&gate.target); 227 } 228 } else { 229 queue.push_back(gate); 230 } 231 } 232 233 let ans = all_zs.into_iter().enumerate().fold(0_usize, |acc, (i, z)| { 234 let wire = wires.get(z).unwrap(); 235 if *wire { 236 acc | (1 << i) 237 } else { 238 acc 239 } 240 }); 241 242 Some(ans.to_string()) 243 } 244 245 fn part_2((_, mut gates): Self::Input) -> Option<String> { 246 let mut swapped = Vec::with_capacity(8); 247 let mut current_carry = String::new(); 248 249 for i in 0.. { 250 let res = if i == 0 { 251 test_first_adder(&gates) 252 } else { 253 test_adder(i, &current_carry, &gates) 254 }; 255 256 match res { 257 AdderTestResult::Okay(new_carry) => { 258 current_carry = new_carry; 259 } 260 AdderTestResult::End => { 261 break; 262 } 263 AdderTestResult::CompletelyWrong => { 264 panic!("Wrong adder"); 265 } 266 AdderTestResult::SwapNeeded(l, r) => { 267 swap_outputs(&mut gates, &l, &r); 268 current_carry = test_adder(i, &current_carry, &gates).unwrap_okay(); 269 swapped.push(l); 270 swapped.push(r); 271 } 272 }; 273 } 274 275 swapped.sort(); 276 277 Some(swapped.join(",")) 278 } 279 280 fn parse_input(input: &str) -> Self::Input { 281 let (inits, gates) = input.trim().split_once("\n\n").unwrap(); 282 283 let wires = inits 284 .lines() 285 .map(|l| { 286 let (name, val) = l.split_once(": ").unwrap(); 287 (name.to_string(), val == "1") 288 }) 289 .collect::<HashMap<_, _>>(); 290 291 let gates = gates 292 .lines() 293 .flat_map(|l| { 294 let gate = Gate::parse(l); 295 let op_str = format!("{:?}", gate.op); 296 [ 297 ( 298 (gate.lhs.clone(), gate.rhs.clone(), op_str.clone()), 299 gate.clone(), 300 ), 301 ((gate.rhs.clone(), gate.lhs.clone(), op_str), gate), 302 ] 303 }) 304 .collect::<HashMap<_, _>>(); 305 306 (wires, gates) 307 } 308}