|
| 1 | +use std::collections::HashMap; |
| 2 | + |
| 3 | +use itertools::Itertools; |
| 4 | + |
| 5 | +use crate::solution::{AocError, Solution}; |
| 6 | + |
| 7 | +#[derive(Debug, PartialEq, Eq, Hash)] |
| 8 | +enum Gate { |
| 9 | + And, |
| 10 | + Or, |
| 11 | + Xor, |
| 12 | +} |
| 13 | + |
| 14 | +type Gates<'a> = Vec<(Gate, &'a str, &'a str, &'a str)>; |
| 15 | +type Registers<'a> = HashMap<&'a str, Option<u8>>; |
| 16 | + |
| 17 | +fn parse(input: &str) -> Result<(Gates, Registers), AocError> { |
| 18 | + let (inputs_str, gates_str) = input |
| 19 | + .split_once("\n\n") |
| 20 | + .ok_or_else(|| AocError::parse(input, "Missing sections"))?; |
| 21 | + |
| 22 | + let mut registers: HashMap<&str, Option<u8>> = HashMap::new(); |
| 23 | + |
| 24 | + for line in inputs_str.lines() { |
| 25 | + let (register, value_input) = line |
| 26 | + .split_once(": ") |
| 27 | + .ok_or_else(|| AocError::parse(line, "Invalid initial value"))?; |
| 28 | + let value = value_input |
| 29 | + .parse::<u8>() |
| 30 | + .map_err(|err| AocError::parse(value_input, err))?; |
| 31 | + |
| 32 | + registers.insert(register, Some(value)); |
| 33 | + } |
| 34 | + |
| 35 | + let mut gates: Vec<(Gate, &str, &str, &str)> = Vec::new(); |
| 36 | + |
| 37 | + for line in gates_str.lines() { |
| 38 | + let (inputs, output) = line |
| 39 | + .split_once(" -> ") |
| 40 | + .ok_or_else(|| AocError::parse(line, "Invalid gate"))?; |
| 41 | + |
| 42 | + let (a, gate, b) = inputs |
| 43 | + .split_ascii_whitespace() |
| 44 | + .collect_tuple() |
| 45 | + .ok_or_else(|| AocError::parse(inputs, "Invalid gate inputs"))?; |
| 46 | + |
| 47 | + let gate = match gate { |
| 48 | + "AND" => Gate::And, |
| 49 | + "OR" => Gate::Or, |
| 50 | + "XOR" => Gate::Xor, |
| 51 | + unknown => return Err(AocError::parse(unknown, "Unsupported gate")), |
| 52 | + }; |
| 53 | + |
| 54 | + registers.entry(a).or_insert(None); |
| 55 | + registers.entry(b).or_insert(None); |
| 56 | + registers.entry(output).or_insert(None); |
| 57 | + |
| 58 | + gates.push((gate, a, b, output)); |
| 59 | + } |
| 60 | + |
| 61 | + Ok((gates, registers)) |
| 62 | +} |
| 63 | + |
| 64 | +fn read_output(registers: HashMap<&str, Option<u8>>) -> u64 { |
| 65 | + let mut bits = registers |
| 66 | + .into_iter() |
| 67 | + .filter(|(name, _value)| name.starts_with("z")) |
| 68 | + .collect::<Vec<_>>(); |
| 69 | + |
| 70 | + bits.sort_by(|a, b| a.0.cmp(b.0)); |
| 71 | + bits.iter().enumerate().fold(0u64, |acc, (i, &(_, bit))| { |
| 72 | + acc | ((bit.unwrap_or(0) as u64) << i) |
| 73 | + }) |
| 74 | +} |
| 75 | + |
| 76 | +fn check_next(gates: &Gates, a: &&str, b: &&str, output: &&str, expected: &[Gate]) -> bool { |
| 77 | + let next_gates: Vec<_> = gates |
| 78 | + .iter() |
| 79 | + .filter(|(_, next_a, next_b, _)| { |
| 80 | + (next_a != a && next_b != b) && (output == next_a || output == next_b) |
| 81 | + }) |
| 82 | + .collect(); |
| 83 | + |
| 84 | + if next_gates.len() != expected.len() { |
| 85 | + return false; |
| 86 | + } |
| 87 | + |
| 88 | + expected.iter().all(|expected_gate| { |
| 89 | + next_gates |
| 90 | + .iter() |
| 91 | + .filter(|(gate, _, _, _)| gate == expected_gate) |
| 92 | + .count() |
| 93 | + == 1 |
| 94 | + }) |
| 95 | +} |
| 96 | + |
| 97 | +pub struct Day24; |
| 98 | +impl Solution for Day24 { |
| 99 | + type A = u64; |
| 100 | + type B = String; |
| 101 | + |
| 102 | + fn default_input(&self) -> &'static str { |
| 103 | + include_str!("../../../inputs/2024/day24.txt") |
| 104 | + } |
| 105 | + |
| 106 | + fn part_1(&self, input: &str) -> Result<u64, AocError> { |
| 107 | + let (gates, mut registers) = parse(input)?; |
| 108 | + |
| 109 | + while registers |
| 110 | + .iter() |
| 111 | + .any(|(name, value)| name.starts_with('z') && value.is_none()) |
| 112 | + { |
| 113 | + for (gate, a, b, output) in gates.iter() { |
| 114 | + if let (Some(a), Some(b), None) = (registers[a], registers[b], registers[output]) { |
| 115 | + match gate { |
| 116 | + Gate::And => *registers.entry(output).or_default() = Some(a & b), |
| 117 | + Gate::Or => *registers.entry(output).or_default() = Some(a | b), |
| 118 | + Gate::Xor => *registers.entry(output).or_default() = Some(a ^ b), |
| 119 | + } |
| 120 | + } |
| 121 | + } |
| 122 | + } |
| 123 | + |
| 124 | + Ok(read_output(registers)) |
| 125 | + } |
| 126 | + |
| 127 | + fn part_2(&self, input: &str) -> Result<String, AocError> { |
| 128 | + let (gates, _) = parse(input)?; |
| 129 | + |
| 130 | + // Lets assume this is a perfect Ripple-Carry full adder with no useless gates. |
| 131 | + // After drawing the circuit on paper and inspecting it the following logic emerged: |
| 132 | + |
| 133 | + let last_bit = gates |
| 134 | + .iter() |
| 135 | + .filter_map(|(_, _, _, output)| output.starts_with("z").then_some(output)) |
| 136 | + .max() |
| 137 | + .cloned() |
| 138 | + .ok_or_else(|| AocError::logic("Missing last bit"))?; |
| 139 | + |
| 140 | + let mut incorrect: Vec<_> = gates |
| 141 | + .iter() |
| 142 | + .filter(|(gate, a, b, output)| { |
| 143 | + let is_first_bit = *a == "x00" && *b == "y00" || *a == "y00" && *b == "x00"; |
| 144 | + let is_last_bit = *output == last_bit; |
| 145 | + let is_x_y = (a.starts_with("x") && b.starts_with("y")) |
| 146 | + || (a.starts_with("y") && b.starts_with("x")); |
| 147 | + |
| 148 | + match gate { |
| 149 | + // Z outputs can only be from XOR gates, except from OR gate at the last bit |
| 150 | + _ if output.starts_with("z") && is_last_bit => *gate != Gate::Or, |
| 151 | + _ if output.starts_with("z") => *gate != Gate::Xor, |
| 152 | + // XOR connected to X and Y has to be connected to AND and XOR gates |
| 153 | + Gate::Xor if is_x_y => { |
| 154 | + !check_next(&gates, a, b, output, &[Gate::And, Gate::Xor]) |
| 155 | + } |
| 156 | + // AND has to be connected to OR gate, except to AND and XOR at the first bit with no C_in |
| 157 | + Gate::And if is_first_bit => { |
| 158 | + !check_next(&gates, a, b, output, &[Gate::And, Gate::Xor]) |
| 159 | + } |
| 160 | + Gate::And => !check_next(&gates, a, b, output, &[Gate::Or]), |
| 161 | + // OR has to be connected to AND and XOR gates |
| 162 | + Gate::Or => !check_next(&gates, a, b, output, &[Gate::And, Gate::Xor]), |
| 163 | + // Any other gates are invalid |
| 164 | + _ => true, |
| 165 | + } |
| 166 | + }) |
| 167 | + .map(|(_, _, _, output)| *output) |
| 168 | + .collect(); |
| 169 | + |
| 170 | + incorrect.sort(); |
| 171 | + |
| 172 | + Ok(incorrect.join(",")) |
| 173 | + } |
| 174 | +} |
| 175 | + |
| 176 | +#[cfg(test)] |
| 177 | +mod tests { |
| 178 | + use super::*; |
| 179 | + |
| 180 | + #[test] |
| 181 | + fn it_solves_part1_example_1() { |
| 182 | + assert_eq!( |
| 183 | + Day24.part_1( |
| 184 | + "x00: 1\n\ |
| 185 | + x01: 1\n\ |
| 186 | + x02: 1\n\ |
| 187 | + y00: 0\n\ |
| 188 | + y01: 1\n\ |
| 189 | + y02: 0\n\ |
| 190 | + \n\ |
| 191 | + x00 AND y00 -> z00\n\ |
| 192 | + x01 XOR y01 -> z01\n\ |
| 193 | + x02 OR y02 -> z02" |
| 194 | + ), |
| 195 | + Ok(4) |
| 196 | + ); |
| 197 | + } |
| 198 | + |
| 199 | + #[test] |
| 200 | + fn it_solves_part1_example_2() { |
| 201 | + assert_eq!( |
| 202 | + Day24.part_1( |
| 203 | + "x00: 1\n\ |
| 204 | + x01: 0\n\ |
| 205 | + x02: 1\n\ |
| 206 | + x03: 1\n\ |
| 207 | + x04: 0\n\ |
| 208 | + y00: 1\n\ |
| 209 | + y01: 1\n\ |
| 210 | + y02: 1\n\ |
| 211 | + y03: 1\n\ |
| 212 | + y04: 1\n\ |
| 213 | + \n\ |
| 214 | + ntg XOR fgs -> mjb\n\ |
| 215 | + y02 OR x01 -> tnw\n\ |
| 216 | + kwq OR kpj -> z05\n\ |
| 217 | + x00 OR x03 -> fst\n\ |
| 218 | + tgd XOR rvg -> z01\n\ |
| 219 | + vdt OR tnw -> bfw\n\ |
| 220 | + bfw AND frj -> z10\n\ |
| 221 | + ffh OR nrd -> bqk\n\ |
| 222 | + y00 AND y03 -> djm\n\ |
| 223 | + y03 OR y00 -> psh\n\ |
| 224 | + bqk OR frj -> z08\n\ |
| 225 | + tnw OR fst -> frj\n\ |
| 226 | + gnj AND tgd -> z11\n\ |
| 227 | + bfw XOR mjb -> z00\n\ |
| 228 | + x03 OR x00 -> vdt\n\ |
| 229 | + gnj AND wpb -> z02\n\ |
| 230 | + x04 AND y00 -> kjc\n\ |
| 231 | + djm OR pbm -> qhw\n\ |
| 232 | + nrd AND vdt -> hwm\n\ |
| 233 | + kjc AND fst -> rvg\n\ |
| 234 | + y04 OR y02 -> fgs\n\ |
| 235 | + y01 AND x02 -> pbm\n\ |
| 236 | + ntg OR kjc -> kwq\n\ |
| 237 | + psh XOR fgs -> tgd\n\ |
| 238 | + qhw XOR tgd -> z09\n\ |
| 239 | + pbm OR djm -> kpj\n\ |
| 240 | + x03 XOR y03 -> ffh\n\ |
| 241 | + x00 XOR y04 -> ntg\n\ |
| 242 | + bfw OR bqk -> z06\n\ |
| 243 | + nrd XOR fgs -> wpb\n\ |
| 244 | + frj XOR qhw -> z04\n\ |
| 245 | + bqk OR frj -> z07\n\ |
| 246 | + y03 OR x01 -> nrd\n\ |
| 247 | + hwm AND bqk -> z03\n\ |
| 248 | + tgd XOR rvg -> z12\n\ |
| 249 | + tnw OR pbm -> gnj" |
| 250 | + ), |
| 251 | + Ok(2024) |
| 252 | + ); |
| 253 | + } |
| 254 | + |
| 255 | + #[test] |
| 256 | + fn it_solves_part2_real() { |
| 257 | + assert_eq!( |
| 258 | + Day24.part_2(Day24.default_input()), |
| 259 | + Ok(String::from("drg,gvw,jbp,jgc,qjb,z15,z22,z35")) |
| 260 | + ) |
| 261 | + } |
| 262 | +} |
0 commit comments