Skip to content

Commit f9ed53c

Browse files
committed
Day 24 and 25 solutions after some refactoring
1 parent 96d7fe8 commit f9ed53c

File tree

7 files changed

+4747
-7
lines changed

7 files changed

+4747
-7
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,5 @@ This should start the server at `localhost:8080`.
109109
❄️ [Day 21](aoc-solver/src/y2024/day21.rs)
110110
❄️ [Day 22](aoc-solver/src/y2024/day22.rs)
111111
❄️ [Day 23](aoc-solver/src/y2024/day23.rs)
112+
❄️ [Day 24](aoc-solver/src/y2024/day24.rs)
113+
❄️ [Day 25](aoc-solver/src/y2024/day25.rs)

aoc-solver/src/y2024/day24.rs

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
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

Comments
 (0)