Advent of Code - Day 17: Chronospatial Computer


2024-12-17-generated.png

The Historians push the button on their strange device, but this time, you all just feel like you’re falling.

-- Day 17 - Advent of Code 2024

Solution in Java

Full source can be found: in GitHub

Part 1

The first thing for today is modelling Opcode and the ComputeEngine. Where the compute engine represents the machine that is executing the instruction in the input string.

 1enum Opcode {  
 2    adv, bxl, bst, jnz, bxc, out, bdv, cdv  
 3}
 4
 5public static class ComputeEngine {  
 6    private final long[] registers;  
 7  
 8    int[] program;  
 9    int instructionPointer;  
10  
11    List<Integer> output = new ArrayList<>();  
12  
13    public ComputeEngine(long[] registers, int[] program) {  
14        this.registers = registers;  
15        this.program = program;  
16        this.instructionPointer = 0;  
17    }
18}

Using these two entities it is already possible to create the method that converts the input string.

 1public void readInput() {  
 2    var lines = inputLoader.split("\n");  
 3    engine = new ComputeEngine(  
 4            new long[]{Integer.parseInt(lines[0].substring(12)),  
 5                    Integer.parseInt(lines[1].substring(12)),  
 6                    Integer.parseInt(lines[2].substring(12))},  
 7            Arrays.stream(lines[4].substring(9)  
 8                    .split(",")).mapToInt(Integer::parseInt).toArray()  
 9    );  
10}

To be able to actually run the input line I added the run() method to the MachineEngine. This run will iterate over the array of program to take the instruction and the opcode and compute what is supposed to be done.

 1public String run() {  
 2    while (instructionPointer < program.length) {  
 3        var combo = switch (program[instructionPointer + 1]) {  
 4            case 4 -> registers[0];  
 5            case 5 -> registers[1];  
 6            case 6 -> registers[2];  
 7            default -> program[instructionPointer + 1];  
 8        };  
 9  
10        var opcode = Opcode.class.getEnumConstants()[program[instructionPointer]];  
11        switch (opcode) {  
12            case adv -> registers[A] = registers[A] / (1L << combo);  
13            case bxl -> registers[B] ^= program[instructionPointer + 1];  
14            case bst -> registers[B] = combo & 7;  
15            case jnz -> {  
16                if (registers[A] != 0) {  
17                    instructionPointer = program[instructionPointer + 1] - 2;  
18                }  
19            }  
20            case bxc -> registers[B] ^= registers[C];  
21            case bdv -> registers[B] = registers[A] / (1L << combo);  
22            case cdv -> registers[C] = registers[A] / (1L << combo);  
23            case out -> output.add((int) combo & 7);  
24        }  
25  
26        instructionPointer += 2;  
27    }  
28  
29    return output.stream()  
30            .map(String::valueOf)  
31            .collect(Collectors.joining(","));  
32}

With the updated MachineEngine the code for part 1 of today can be completed using the following code.

1public void part1() {  
2    validator.part1(engine.run());  
3}

Part 2

For the solution of part 2 I added some code that attempts to search for a similar output to the input. This is done using a recursive loop comparing the end of the output with the end of the input.

 1public void part2() {  
 2    validator.part2(find(engine.program.length - 1, 0));  
 3}
 4
 5private long find(int pointer, long a) {  
 6    if (pointer == -1) {  
 7        return a;  
 8    }  
 9  
10    a = a * 8;  
11    for (var i = 0; i < 8; i++) {  
12        engine.reset(a + i, 0, 0);  
13        engine.run();  
14        if (engine.tailEqual()) {  
15            long candidate = find(pointer - 1, a + i);  
16            if (candidate >= 0) {  
17                return candidate;  
18            }  
19        }  
20    }  
21  
22    return -1;  
23}

To make this work I added a method tailEqual to the ComputeEngine class to check the end of the program against the output to make sure they are still equal.

1public boolean tailEqual() {  
2    int d = program.length - output.size();  
3    for (int i = 0; i < output.size(); i++) {  
4        if (program[d + i] != output.get(i)) {  
5            return false;  
6        }  
7    }  
8    return true;  
9}

See also