The Historians push the button on their strange device, but this time, you all just feel like you’re falling.
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}