Advent of Code solutions

Day 24 & 25

bwc9876.dev 5a990294 daac2043

verified
+365 -49
+1
.gitignore
··· 8 8 9 9 # Nice one rustc 10 10 rustc-ice* 11 + *.dot 11 12
+25 -38
Cargo.lock
··· 42 42 43 43 [[package]] 44 44 name = "console" 45 - version = "0.15.8" 45 + version = "0.15.10" 46 46 source = "registry+https://github.com/rust-lang/crates.io-index" 47 - checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" 47 + checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" 48 48 dependencies = [ 49 49 "encode_unicode", 50 - "lazy_static", 51 50 "libc", 52 - "unicode-width 0.1.14", 51 + "once_cell", 52 + "unicode-width", 53 53 "windows-sys", 54 54 ] 55 55 ··· 86 86 87 87 [[package]] 88 88 name = "encode_unicode" 89 - version = "0.3.6" 89 + version = "1.0.0" 90 90 source = "registry+https://github.com/rust-lang/crates.io-index" 91 - checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 91 + checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" 92 92 93 93 [[package]] 94 94 name = "indicatif" ··· 99 99 "console", 100 100 "number_prefix", 101 101 "portable-atomic", 102 - "unicode-width 0.2.0", 102 + "unicode-width", 103 103 "web-time", 104 104 ] 105 105 106 106 [[package]] 107 107 name = "js-sys" 108 - version = "0.3.74" 108 + version = "0.3.76" 109 109 source = "registry+https://github.com/rust-lang/crates.io-index" 110 - checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" 110 + checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" 111 111 dependencies = [ 112 112 "once_cell", 113 113 "wasm-bindgen", 114 114 ] 115 115 116 116 [[package]] 117 - name = "lazy_static" 118 - version = "1.5.0" 119 - source = "registry+https://github.com/rust-lang/crates.io-index" 120 - checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 121 - 122 - [[package]] 123 117 name = "libc" 124 - version = "0.2.167" 118 + version = "0.2.169" 125 119 source = "registry+https://github.com/rust-lang/crates.io-index" 126 - checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" 120 + checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" 127 121 128 122 [[package]] 129 123 name = "log" ··· 231 225 232 226 [[package]] 233 227 name = "syn" 234 - version = "2.0.90" 228 + version = "2.0.91" 235 229 source = "registry+https://github.com/rust-lang/crates.io-index" 236 - checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" 230 + checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035" 237 231 dependencies = [ 238 232 "proc-macro2", 239 233 "quote", ··· 248 242 249 243 [[package]] 250 244 name = "unicode-width" 251 - version = "0.1.14" 252 - source = "registry+https://github.com/rust-lang/crates.io-index" 253 - checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 254 - 255 - [[package]] 256 - name = "unicode-width" 257 245 version = "0.2.0" 258 246 source = "registry+https://github.com/rust-lang/crates.io-index" 259 247 checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" ··· 264 252 265 253 [[package]] 266 254 name = "wasm-bindgen" 267 - version = "0.2.97" 255 + version = "0.2.99" 268 256 source = "registry+https://github.com/rust-lang/crates.io-index" 269 - checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" 257 + checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" 270 258 dependencies = [ 271 259 "cfg-if", 272 260 "once_cell", ··· 275 263 276 264 [[package]] 277 265 name = "wasm-bindgen-backend" 278 - version = "0.2.97" 266 + version = "0.2.99" 279 267 source = "registry+https://github.com/rust-lang/crates.io-index" 280 - checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" 268 + checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" 281 269 dependencies = [ 282 270 "bumpalo", 283 271 "log", 284 - "once_cell", 285 272 "proc-macro2", 286 273 "quote", 287 274 "syn", ··· 290 277 291 278 [[package]] 292 279 name = "wasm-bindgen-macro" 293 - version = "0.2.97" 280 + version = "0.2.99" 294 281 source = "registry+https://github.com/rust-lang/crates.io-index" 295 - checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" 282 + checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" 296 283 dependencies = [ 297 284 "quote", 298 285 "wasm-bindgen-macro-support", ··· 300 287 301 288 [[package]] 302 289 name = "wasm-bindgen-macro-support" 303 - version = "0.2.97" 290 + version = "0.2.99" 304 291 source = "registry+https://github.com/rust-lang/crates.io-index" 305 - checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" 292 + checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" 306 293 dependencies = [ 307 294 "proc-macro2", 308 295 "quote", ··· 313 300 314 301 [[package]] 315 302 name = "wasm-bindgen-shared" 316 - version = "0.2.97" 303 + version = "0.2.99" 317 304 source = "registry+https://github.com/rust-lang/crates.io-index" 318 - checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" 305 + checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" 319 306 320 307 [[package]] 321 308 name = "web-time" ··· 329 316 330 317 [[package]] 331 318 name = "windows-sys" 332 - version = "0.52.0" 319 + version = "0.59.0" 333 320 source = "registry+https://github.com/rust-lang/crates.io-index" 334 - checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 321 + checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 335 322 dependencies = [ 336 323 "windows-targets", 337 324 ]
+296 -6
years/2024/src/day_24.rs
··· 1 + use std::collections::{HashMap, HashSet, VecDeque}; 1 2 2 - use advent_core::{Day, day_stuff, ex_for_day}; 3 + use advent_core::{day_stuff, ex_for_day, Day}; 3 4 4 5 pub struct Day24; 5 6 7 + pub type Wires = HashMap<String, bool>; 8 + pub type Gates = HashMap<(String, String, String), Gate>; 9 + 10 + fn 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)] 15 + pub enum Op { 16 + And, 17 + Or, 18 + Xor, 19 + } 20 + 21 + impl 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)] 32 + pub struct Gate { 33 + lhs: String, 34 + op: Op, 35 + rhs: String, 36 + target: String, 37 + } 38 + 39 + impl 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.skip(1).next().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 + 70 + enum AdderTestResult { 71 + Okay(String), 72 + SwapNeeded(String, String), 73 + CompletelyWrong, 74 + End, 75 + } 76 + 77 + impl 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 + // 91 + fn 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 + // 137 + fn test_adder(num: usize, carry_input: &String, 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 + 192 + fn 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 + 6 205 impl Day for Day24 { 206 + day_stuff!(24, "", "", (Wires, Gates)); 7 207 8 - day_stuff!(24, "", ""); 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 + 216 + let mut current_zs = HashSet::<&String>::with_capacity(all_zs.len()); 217 + 218 + let mut queue = gates.values().collect::<VecDeque<_>>(); 219 + 220 + while let Some(gate) = queue.pop_front() 221 + && current_zs.len() < all_zs.len() 222 + { 223 + if gate.run(&mut wires) { 224 + if gate.target.starts_with('z') { 225 + current_zs.insert(&gate.target); 226 + } 227 + } else { 228 + queue.push_back(gate); 229 + } 230 + } 231 + 232 + let ans = all_zs.into_iter().enumerate().fold(0_usize, |acc, (i, z)| { 233 + let wire = wires.get(z).unwrap(); 234 + if *wire { 235 + acc | (1 << i) 236 + } else { 237 + acc 238 + } 239 + }); 240 + 241 + Some(ans.to_string()) 242 + } 243 + 244 + fn part_2((_, mut gates): Self::Input) -> Option<String> { 245 + let mut swapped = Vec::with_capacity(8); 246 + let mut current_carry = String::new(); 247 + 248 + for i in 0.. { 249 + let res = if i == 0 { 250 + test_first_adder(&gates) 251 + } else { 252 + test_adder(i, &current_carry, &gates) 253 + }; 254 + 255 + match res { 256 + AdderTestResult::Okay(new_carry) => { 257 + current_carry = new_carry; 258 + } 259 + AdderTestResult::End => { 260 + break; 261 + } 262 + AdderTestResult::CompletelyWrong => { 263 + panic!("Wrong adder"); 264 + } 265 + AdderTestResult::SwapNeeded(l, r) => { 266 + swap_outputs(&mut gates, &l, &r); 267 + current_carry = test_adder(i, &current_carry, &gates).unwrap_okay(); 268 + swapped.push(l); 269 + swapped.push(r); 270 + } 271 + }; 272 + } 9 273 10 - fn part_1(_input: Self::Input) -> Option<String> { 11 - None 274 + swapped.sort(); 275 + 276 + Some(swapped.join(",")) 12 277 } 13 278 14 - fn part_2(_input: Self::Input) -> Option<String> { 15 - None 279 + fn parse_input(input: &str) -> Self::Input { 280 + let (inits, gates) = input.trim().split_once("\n\n").unwrap(); 281 + 282 + let wires = inits 283 + .lines() 284 + .map(|l| { 285 + let (name, val) = l.split_once(": ").unwrap(); 286 + (name.to_string(), val == "1") 287 + }) 288 + .collect::<HashMap<_, _>>(); 289 + 290 + let gates = gates 291 + .lines() 292 + .flat_map(|l| { 293 + let gate = Gate::parse(l); 294 + let op_str = format!("{:?}", gate.op); 295 + [ 296 + ( 297 + (gate.lhs.clone(), gate.rhs.clone(), op_str.clone()), 298 + gate.clone(), 299 + ), 300 + ((gate.rhs.clone(), gate.lhs.clone(), op_str), gate), 301 + ] 302 + }) 303 + .collect::<HashMap<_, _>>(); 304 + 305 + (wires, gates) 16 306 } 17 307 }
+43 -5
years/2024/src/day_25.rs
··· 1 + use std::collections::HashSet; 1 2 2 - use advent_core::{Day, day_stuff, ex_for_day}; 3 + use advent_core::{day_stuff, ex_for_day, Day}; 4 + use utils::{tiles, upos}; 3 5 4 6 pub struct Day25; 5 7 8 + tiles!(Tile, [ 9 + '#' => Fill, 10 + '.' => Empty, 11 + ]); 12 + 13 + type Grid = utils::grid::Grid<Tile>; 14 + 6 15 impl Day for Day25 { 16 + day_stuff!(25, "", "", (HashSet<[u8; 5]>, HashSet<[u8; 5]>)); 7 17 8 - day_stuff!(25, "", ""); 18 + fn part_1((locks, keys): Self::Input) -> Option<String> { 19 + let ans = locks 20 + .into_iter() 21 + .flat_map(|l| { 22 + keys.iter() 23 + .filter(move |k| l.iter().zip(k.iter()).all(|(l, k)| *k <= (5 - *l))) 24 + }) 25 + .count(); 9 26 10 - fn part_1(_input: Self::Input) -> Option<String> { 11 - None 27 + Some(ans.to_string()) 12 28 } 13 29 14 30 fn part_2(_input: Self::Input) -> Option<String> { 15 - None 31 + Some("🥳".to_string()) 32 + } 33 + 34 + fn parse_input(input: &str) -> Self::Input { 35 + let mut locks = HashSet::new(); 36 + let mut keys = HashSet::new(); 37 + 38 + for grid in input.trim().split("\n\n").map(Grid::parse) { 39 + let code = grid 40 + .iter_cols() 41 + .map(|col| (col.filter(|t| **t == Tile::Fill).count() - 1) as u8) 42 + .collect::<Vec<_>>(); 43 + 44 + let code = [code[0], code[1], code[2], code[3], code[4]]; 45 + 46 + if grid.get(upos!(0, 0)).is_some_and(|t| *t == Tile::Fill) { 47 + locks.insert(code); 48 + } else { 49 + keys.insert(code); 50 + } 51 + } 52 + 53 + (locks, keys) 16 54 } 17 55 }