Metadata
-
Date
-
Tagged
-
Part of series
- Advent of Code 2024 Day 1
- Advent of Code 2024 Day 2
- Advent of Code 2024 Day 3
- Advent of Code 2024 Day 4
- Advent of Code 2024 Day 5
- Advent of Code 2024 Day 6
- Advent of Code 2024 Day 7
- Advent of Code 2024 Day 8
- Advent of Code 2024 Day 9
- Advent of Code 2024 Day 10
- Advent of Code 2024 Day 11
- Advent of Code 2024 Day 12
- Advent of Code 2024 Day 13
- Advent of Code 2024 Day 14
- Advent of Code 2024 Day 15
- Advent of Code 2024 Day 16
- Advent of Code 2024 Day 17
- Advent of Code 2024 Day 18
- Advent of Code 2024 Day 19
- Advent of Code 2024 Day 20
-
Older post
-
Newer post
Advent of Code 2024 Day 17
Day 17: Chronospatial Computer
https://adventofcode.com/2024/day/17
Another day, another … familiar sensation this time, as you’re falling.
You need to debug the handheld timetravel/teleportation-device you have been using throughout this advent.
It has a computer with 3 registers, those are storage spots for numbers. The program it is trying to run consists of a list of numbers, and it is listed at the bottom of the input.
An example input looks like this:
Parsing
I parsed today’s input into a list of numbers, nothing fancy.
- first number is A
- second number is B
- third number is C
- the rest of the numbers are the program
Part 1
The computer uses an instruction pointer, it functions as an index into the program. That instruction pointer starts as 0, so it starts off pointing at the first number in the program (a list of numbers).
A computer looks like this:
The program can do 8 operations.
Each operation uses 2 numbers from the program memory.
- The opcode at
program[ip]
- The operand at
program[ip + 2]
Per time it does an operation, I increment the ip
(instruction pointer) by 2 to move past the 2 numbers the computer just used.
Which type of instruction is executed is determined by the opcode.
It might change the numbers in the registers, or change the ip
.
The operand might get modified before it gets used by an instruction. There are 2 options:
- literal operand: operand remains unchanged
- combo operand:
- operands 0 through 3 remain unchanged
- operand 4 turn into the value of register A.
- operand 5 turn into the value of register B.
- operand 6 turn into the value of register C.
- operand 7 is reserved and will not appear in valid programs.
The 8 instructions:
**
represents exponentiation.^
represent the bitwise XOR operation%
represents a modulo operation
adv
:a / (2 ** combo)
, store result ina
bxl
:b ^ literal
, store result in theb
bst
:combo % 8
, store result inb
jnz
: changeip
to literal operand ifa
is not 0bxc
:b ^ c
store result inb
(this operation ignores its operand)out
:combo % 8
, output resultbdv
:a / (2 ** combo)
, store result inb
cdv
:a / (2 ** combo)
, store result inc
Helpers
I used a data structure that holds a computer.
- It has a method to turn an operand into a combo operand.
- It has a method that runs the program until it outputs a number or halts (the instruction pointer points outside of the program memory).
Code
The question asks you to print the all the numbers the computer outputs, and place a comma between each one.
Part 2
The program is supposed to output another copy of the input program, but it doesn’t, because the input value in register A is corrupt.
The question asks what the lowest valid number for A is (so the program outputs another copy of itself).
My solution (which heavily uses some help I got in the Rust Adventure Discord) makes a few assumptions about the input and uses them.
-
Your program ends in
3,0
. That means at the end of the program the value ofa
must be 0. -
The initial values for
b
andc
do not matter as they are set entirely based on the initial value ina
. -
There is only one operation in the program that modifies
a
,
and it isa / (2 ** 3)
. That’s equivalent toa >> 3
.
So I used backtracking to find the original value for a
, given the program numbers.
The last valid value for a
is 0
, because that’s necessary for the program to end.
Starting with the last number the program should output and working backwards.
Each loop, I try to figure out what the previous number for a
could have been given a valid value for a
in the next loop,
and the number the program outputs.
I keep track of these possible valid values in a list, as each loop can result in multiple valid numbers a
could have been.
I use the numbers in that list, call one valid_next_a
, and I know that number is the result of a >> 3
.
In other words:
next_a = a >> 3
.
The value for next_a
is known, so I try all possible values for a
and see if they cause the correct program output.
That value the inverse of the operation + a number that has no inpact on the operation:
a = (next_a << 3) + something_with_3_bits