|
1 | | -//! Advent of Code 2024: Day 17 |
2 | | -//! https://adventofcode.com/2024/day/17 |
3 | | -
|
4 | | -use std::{fs, io}; |
5 | | -use std::path::Path; |
6 | | - |
7 | | -fn main() { |
8 | | - //let input = Input::from_file(format!("{}/input.txt", env!("CARGO_MANIFEST_DIR"))).expect("failed to read input"); |
9 | | - let input = Input::from_file(format!("{}/example2.txt", env!("CARGO_MANIFEST_DIR"))).expect("failed to read input"); |
10 | | - |
11 | | - // Part 1 |
12 | | - println!("Part 1: {}", part1(&input)); |
13 | | - |
14 | | - // Part 2 |
15 | | - let p2 = part2(&input); |
16 | | - assert_eq!(State::new([p2, 0, 0]).run(&input.program), input.program); |
17 | | - //println!("Part 2: {}", part2(&input)); |
18 | | -} |
19 | | - |
20 | | -#[derive(Copy, Clone, Debug, Eq, PartialEq)] |
21 | | -enum Op { |
22 | | - /// Division (output Register A) |
23 | | - Adv, |
24 | | - /// Bitwise XOR of Register B and literal |
25 | | - Bxl, |
26 | | - /// Combo |
27 | | - Bst, |
28 | | - /// Jump Non-Zero |
29 | | - Jnz, |
30 | | - /// Bitwise XOR of Register B and Register C |
31 | | - Bxc, |
32 | | - /// Output |
33 | | - Out, |
34 | | - /// Division (output Register B) |
35 | | - Bdv, |
36 | | - /// Division (output Register C) |
37 | | - Cdv, |
38 | | -} |
39 | | - |
40 | | -impl TryFrom<usize> for Op { |
41 | | - type Error = usize; |
42 | | - |
43 | | - fn try_from(value: usize) -> Result<Self, Self::Error> { |
44 | | - match value { |
45 | | - 0 => Ok(Self::Adv), |
46 | | - 1 => Ok(Self::Bxl), |
47 | | - 2 => Ok(Self::Bst), |
48 | | - 3 => Ok(Self::Jnz), |
49 | | - 4 => Ok(Self::Bxc), |
50 | | - 5 => Ok(Self::Out), |
51 | | - 6 => Ok(Self::Bdv), |
52 | | - 7 => Ok(Self::Cdv), |
53 | | - opcode => Err(opcode) |
54 | | - } |
55 | | - } |
56 | | -} |
57 | | - |
58 | | -const BITMASK: usize = 0b111; |
59 | | - |
60 | | -#[derive(Debug, Clone)] |
61 | | -struct State { |
62 | | - ip: usize, |
63 | | - regs: [usize; 3], |
64 | | -} |
65 | | - |
66 | | -impl State { |
67 | | - fn new(regs: [usize; 3]) -> Self { |
68 | | - State { |
69 | | - ip: 0, |
70 | | - regs, |
71 | | - } |
72 | | - } |
73 | | - |
74 | | - fn combo(&self, operand: usize) -> usize { |
75 | | - match operand { |
76 | | - 0..=3 => operand, |
77 | | - 4 => self.regs[REG_A], |
78 | | - 5 => self.regs[REG_B], |
79 | | - 6 => self.regs[REG_C], |
80 | | - _ => panic!("invalid combo operand: {operand}"), |
81 | | - } |
82 | | - } |
83 | | - |
84 | | - fn run(&mut self, program: &[usize]) -> Vec<usize> { |
85 | | - let mut ip = 0; |
86 | | - let mut output = vec![]; |
87 | | - |
88 | | - while ip < program.len() { |
89 | | - let op = Op::try_from(program[ip]).unwrap(); |
90 | | - let operand = program[ip + 1]; |
91 | | - ip += 2; |
92 | | - |
93 | | - match op { |
94 | | - Op::Adv => { |
95 | | - self.regs[REG_A] = self.regs[REG_A] >> self.combo(operand); |
96 | | - } |
97 | | - Op::Bxl => { |
98 | | - self.regs[REG_B] = self.regs[REG_B] ^ operand; |
99 | | - } |
100 | | - Op::Bst => { |
101 | | - self.regs[REG_B] = self.combo(operand) & BITMASK; |
102 | | - } |
103 | | - Op::Jnz => { |
104 | | - if self.regs[REG_A] != 0 { |
105 | | - ip = operand; |
106 | | - } |
107 | | - } |
108 | | - Op::Bxc => { |
109 | | - self.regs[REG_B] = self.regs[REG_B] ^ self.regs[REG_C]; |
110 | | - } |
111 | | - Op::Out => { |
112 | | - output.push(self.combo(operand) & BITMASK); |
113 | | - } |
114 | | - Op::Bdv => { |
115 | | - self.regs[REG_B] = self.regs[REG_A] >> self.combo(operand); |
116 | | - } |
117 | | - Op::Cdv => { |
118 | | - self.regs[REG_C] = self.regs[REG_A] >> self.combo(operand); |
119 | | - } |
120 | | - } |
121 | | - } |
122 | | - |
123 | | - output |
124 | | - } |
125 | | - |
126 | | - fn run2(&mut self, program: &[usize]) -> usize { |
127 | | - let mut acc = 0; |
128 | | - |
129 | | - let mut z = 0; |
130 | | - 'outer: for x in program.iter().copied().rev() { |
131 | | - println!("{x}: acc={acc}"); |
132 | | - |
133 | | - for n in 0..=0b1111111111 { |
134 | | - self.regs = [acc << 6 | n, 0, 0]; |
135 | | - |
136 | | - let mut ip = 0; |
137 | | - while ip < program.len() { |
138 | | - let op = Op::try_from(program[ip]).unwrap(); |
139 | | - let operand = program[ip + 1]; |
140 | | - ip += 2; |
141 | | - |
142 | | - match op { |
143 | | - Op::Adv => { |
144 | | - self.regs[REG_A] = self.regs[REG_A] >> self.combo(operand); |
145 | | - } |
146 | | - Op::Bxl => { |
147 | | - self.regs[REG_B] = self.regs[REG_B] ^ operand; |
148 | | - } |
149 | | - Op::Bst => { |
150 | | - self.regs[REG_B] = self.combo(operand) & BITMASK; |
151 | | - } |
152 | | - Op::Jnz => { |
153 | | - if self.regs[REG_A] != 0 { |
154 | | - ip = operand; |
155 | | - } |
156 | | - } |
157 | | - Op::Bxc => { |
158 | | - self.regs[REG_B] = self.regs[REG_B] ^ self.regs[REG_C]; |
159 | | - } |
160 | | - Op::Out => { |
161 | | - if self.combo(operand) & BITMASK == x { |
162 | | - acc = (acc << 3) | (n >> 3); |
163 | | - z += 1; |
164 | | - if z == program.len() { |
165 | | - acc = (acc << 3) | (n & 0b111); |
166 | | - } |
167 | | - continue 'outer; |
168 | | - } |
169 | | - } |
170 | | - Op::Bdv => { |
171 | | - self.regs[REG_B] = self.regs[REG_A] >> self.combo(operand); |
172 | | - } |
173 | | - Op::Cdv => { |
174 | | - self.regs[REG_C] = self.regs[REG_A] >> self.combo(operand); |
175 | | - } |
176 | | - } |
177 | | - |
178 | | - } |
179 | | - } |
180 | | - panic!(); |
181 | | - } |
182 | | - |
183 | | - println!("acc = {acc}"); |
184 | | - acc |
185 | | - } |
186 | | - |
187 | | - fn combo2(&mut self, operand: usize, value: usize) { |
188 | | - match operand { |
189 | | - 0 => assert_eq!(value, 0), |
190 | | - 1 => assert_eq!(value, 1), |
191 | | - 2 => assert_eq!(value, 2), |
192 | | - 3 => assert_eq!(value, 3), |
193 | | - 4 => self.regs[REG_A] = (self.regs[REG_A] & !0b111) | (value & 0b111), |
194 | | - 5 => self.regs[REG_B] = (self.regs[REG_B] & !0b111) | (value & 0b111), |
195 | | - 6 => self.regs[REG_C] = (self.regs[REG_C] & !0b111) | (value & 0b111), |
196 | | - _ => panic!("unknown combo operand {operand}"), |
197 | | - } |
198 | | - } |
199 | | -} |
200 | | - |
201 | | -fn part1(input: &Input) -> String { |
202 | | - let output = State::new(input.registers).run(&input.program); |
203 | | - |
204 | | - let output: Vec<_> = output.into_iter().map(|x| x.to_string()).collect(); |
205 | | - |
206 | | - output.join(",") |
207 | | -} |
208 | | - |
209 | | -fn part2(input: &Input) -> usize { |
210 | | - State::new(input.registers).run2(&input.program) |
211 | | -} |
212 | | - |
213 | | -const REG_A: usize = 0; |
214 | | -const REG_B: usize = 1; |
215 | | -const REG_C: usize = 2; |
216 | | - |
217 | | -#[derive(Debug, Clone)] |
218 | | -struct Input { |
219 | | - registers: [usize; 3], |
220 | | - program: Vec<usize>, |
221 | | -} |
222 | | - |
223 | | -impl Input { |
224 | | - fn from_file(path: impl AsRef<Path>) -> io::Result<Self> { |
225 | | - let input = fs::read_to_string(path)?; |
226 | | - let (chunk1, chunk2) = input.split_once("\n\n").unwrap(); |
227 | | - |
228 | | - let mut registers = [0; 3]; |
229 | | - for (n, line) in chunk1.lines().enumerate() { |
230 | | - let (_, value) = line.trim().split_once(": ").unwrap(); |
231 | | - registers[n] = value.parse().unwrap(); |
232 | | - } |
233 | | - |
234 | | - let (_, program) = chunk2.split_once(": ").unwrap(); |
235 | | - let program = program.trim().split(',').map(|s| s.parse().unwrap()).collect(); |
236 | | - |
237 | | - Ok(Self { registers, program }) |
238 | | - } |
239 | | -} |
240 | | - |
241 | | -#[cfg(test)] |
242 | | -mod test { |
243 | | - use super::*; |
244 | | - |
245 | | - #[test] |
246 | | - fn test_part1() { |
247 | | - let input = Input::from_file("example1.txt").unwrap(); |
248 | | - |
249 | | - assert_eq!(part1(&input), "4,6,3,5,6,3,5,2,1,0"); |
250 | | - } |
251 | | - |
252 | | - #[test] |
253 | | - fn test_part1_solution() { |
254 | | - let input = Input::from_file("input.txt").unwrap(); |
255 | | - |
256 | | - assert_eq!(part1(&input), "5,1,3,4,3,7,2,1,7"); |
257 | | - } |
258 | | - |
259 | | - #[test] |
260 | | - fn test_part2() { |
261 | | - let input = Input::from_file("example2.txt").unwrap(); |
262 | | - |
263 | | - assert_eq!(part2(&input), 117440); |
264 | | - } |
265 | | - |
266 | | - #[test] |
267 | | - fn test_part2_solution() { |
268 | | - let input = Input::from_file("input.txt").unwrap(); |
269 | | - |
270 | | - // Not 217269162253005 |
271 | | - assert_eq!(part2(&input), 0); |
272 | | - } |
273 | | -} |
0 commit comments