Skip to content

Commit 71e45d7

Browse files
committed
2024 day 17
1 parent e529ced commit 71e45d7

File tree

2 files changed

+171
-0
lines changed

2 files changed

+171
-0
lines changed

crates/year2024/src/day17.rs

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
use std::ops::ControlFlow;
2+
use utils::prelude::*;
3+
4+
/// Interpreting 3-bit assembly.
5+
///
6+
/// Part 2 assumes the input is structured such that the Nth digit in the output depends only on
7+
/// bits N*3 onwards. This enables working backwards from the right digit, checking 8 possible
8+
/// 3-bit patterns for each digit.
9+
#[derive(Clone, Debug)]
10+
pub struct Day17 {
11+
a_start: u64,
12+
program: Vec<u8>,
13+
}
14+
15+
impl Day17 {
16+
pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
17+
let (a_start, b_start, c_start, program) = parser::u64()
18+
.with_prefix("Register A: ")
19+
.then(parser::u64().with_prefix("\nRegister B: "))
20+
.then(parser::u64().with_prefix("\nRegister C: "))
21+
.then(
22+
parser::number_range(0..=7)
23+
.repeat(b',', 2)
24+
.with_prefix("\n\nProgram: "),
25+
)
26+
.parse_complete(input)?;
27+
28+
if b_start != 0 || c_start != 0 {
29+
return Err(InputError::new(input, 0, "expected B and C to start at 0"));
30+
}
31+
32+
Ok(Self { a_start, program })
33+
}
34+
35+
#[must_use]
36+
pub fn part1(&self) -> String {
37+
let mut output = Vec::new();
38+
39+
self.run(self.a_start, |x| {
40+
output.push(x);
41+
ControlFlow::Continue(())
42+
});
43+
44+
output
45+
.iter()
46+
.map(|n| n.to_string())
47+
.collect::<Vec<_>>()
48+
.join(",")
49+
}
50+
51+
#[inline]
52+
fn run(&self, mut a: u64, mut out_fn: impl FnMut(u8) -> ControlFlow<()>) {
53+
let mut b = 0;
54+
let mut c = 0;
55+
let mut pc = 0;
56+
57+
while pc + 1 < self.program.len() {
58+
let opcode = self.program[pc];
59+
let operand = self.program[pc + 1] as u64;
60+
let combo_operand = || match operand {
61+
0..=3 => operand,
62+
4 => a,
63+
5 => b,
64+
6 => c,
65+
7 => panic!("combo operand 7 is reserved"),
66+
_ => unreachable!(),
67+
};
68+
69+
match opcode {
70+
0 => a /= 1 << combo_operand(), // adv
71+
1 => b ^= operand, // bxl
72+
2 => b = combo_operand() % 8, // bst
73+
3 => {
74+
// jnz
75+
if a != 0 {
76+
pc = operand as usize;
77+
continue;
78+
}
79+
}
80+
4 => b ^= c, // bxc
81+
5 => {
82+
// out
83+
if out_fn((combo_operand() % 8) as u8) == ControlFlow::Break(()) {
84+
return;
85+
}
86+
}
87+
6 => b = a / (1 << combo_operand()), // bdv
88+
7 => c = a / (1 << combo_operand()), // cdv
89+
_ => unreachable!(),
90+
}
91+
92+
pc += 2;
93+
}
94+
}
95+
96+
#[must_use]
97+
pub fn part2(&self) -> u64 {
98+
self.search(0, self.program.len() - 1)
99+
.expect("no solution found")
100+
}
101+
102+
fn search(&self, base: u64, pos: usize) -> Option<u64> {
103+
if pos == 0 {
104+
for x in 0..8 {
105+
let a = base | x;
106+
if self.check_all_matches(a) {
107+
return Some(a);
108+
}
109+
}
110+
return None;
111+
}
112+
113+
for x in 0..8 {
114+
let a = base | (x << (pos * 3));
115+
if self.check_pos_matches(a, pos) {
116+
if let Some(result) = self.search(a, pos - 1) {
117+
return Some(result);
118+
}
119+
}
120+
}
121+
None
122+
}
123+
124+
fn check_pos_matches(&self, a: u64, pos: usize) -> bool {
125+
let mut count = 0;
126+
let mut output = None;
127+
self.run(a, |out| {
128+
if count == pos {
129+
output = Some(out);
130+
ControlFlow::Break(())
131+
} else {
132+
count += 1;
133+
ControlFlow::Continue(())
134+
}
135+
});
136+
output == Some(self.program[pos])
137+
}
138+
139+
fn check_all_matches(&self, a: u64) -> bool {
140+
let mut count = 0;
141+
self.run(a, |out| {
142+
if count >= self.program.len() || self.program[count] != out {
143+
ControlFlow::Break(())
144+
} else {
145+
count += 1;
146+
ControlFlow::Continue(())
147+
}
148+
});
149+
count == self.program.len()
150+
}
151+
}
152+
153+
examples!(Day17 -> (&'static str, u64) [
154+
{
155+
input: "Register A: 729\n\
156+
Register B: 0\n\
157+
Register C: 0\n\
158+
\n\
159+
Program: 0,1,5,4,3,0",
160+
part1: "4,6,3,5,6,3,5,2,1,0"
161+
},
162+
{
163+
input: "Register A: 2024\n\
164+
Register B: 0\n\
165+
Register C: 0\n\
166+
\n\
167+
Program: 0,3,5,4,3,0",
168+
part2: 117440,
169+
},
170+
]);

crates/year2024/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ utils::year!(2024 => year2024, ${
1818
14 => day14::Day14,
1919
15 => day15::Day15<'_>,
2020
16 => day16::Day16,
21+
17 => day17::Day17,
2122
});

0 commit comments

Comments
 (0)