-
Notifications
You must be signed in to change notification settings - Fork 0
/
Day8Part2.java
148 lines (119 loc) · 5.14 KB
/
Day8Part2.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package uk.oczadly.karl.aoc20.solution.day8;
import uk.oczadly.karl.aoc20.input.NoValidSolutionException;
import uk.oczadly.karl.aoc20.PuzzleSolution;
import uk.oczadly.karl.aoc20.input.IllegalInputException;
import uk.oczadly.karl.aoc20.input.PuzzleInput;
import uk.oczadly.karl.aoc20.util.EnumIndex;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Karl Oczadly
*/
public class Day8Part2 extends PuzzleSolution {
public Day8Part2() { super(8, 2); } // Initializes the day and part number
@Override
public Object solve(PuzzleInput input) {
// Load virtual machine image
VirtualMachine vm = new VirtualMachine(input.asList(Instruction::parse));
// Try modifying each instruction, one by one
Solver solver = new Solver(vm);
for (Instruction instr : vm.instructions) {
Operation op = instr.op;
if (op == Operation.JUMP || op == Operation.NO_OP) {
instr.op = op == Operation.JUMP ? Operation.NO_OP : Operation.JUMP; // Swap operation
if (solver.doesExecute()) {
// Found a working solution
return vm.accumulator;
}
instr.op = op; // Didn't fix - return op to previous
}
}
throw new NoValidSolutionException();
}
/**
* Helper class to test whether the VM executes it's loaded program.
* This class is used so the 'executed' array can be re-used between attempts, improving performance.
*/
static class Solver {
final VirtualMachine vm;
final boolean[] executed;
public Solver(VirtualMachine vm) {
this.vm = vm;
this.executed = new boolean[vm.instructions.size()];
}
/** Returns true if the program loaded into the VM executes successfully. */
public boolean doesExecute() {
Arrays.fill(executed, false); // Clear re-used array
vm.reset(); // Reset the VM's state
do {
if (vm.instrIndex >= vm.instructions.size()) break;
if (executed[vm.instrIndex])
return false; // Infinite loop found
executed[vm.instrIndex] = true;
} while (vm.execute());
// Return true if the program ended on the last instruction index + 1
return vm.instrIndex == vm.instructions.size();
}
}
static class VirtualMachine {
int instrIndex, accumulator;
final List<Instruction> instructions;
public VirtualMachine(List<Instruction> instructions) {
this.instructions = instructions;
}
/** Executes the next instruction. Returns false if the end of the program has been reached. */
public boolean execute() {
Instruction instr = getNextInstruction();
if (instr == null) return false; // End of program
instr.execute(this);
instrIndex++; // Step to next instruction
return true;
}
public void reset() {
this.instrIndex = 0;
this.accumulator = 0;
}
/** Returns the next instruction, or null if the end of the program has been reached. */
public Instruction getNextInstruction() {
if (instrIndex < 0) throw new IllegalStateException("Invalid instruction index.");
if (instrIndex >= instructions.size()) return null; // End of program
return instructions.get(instrIndex);
}
}
/** Not really necessary, could just use split instead. */
static final Pattern INSTRUCTION_PATTERN = Pattern.compile("^(\\w+) ([+\\-]\\d+)$");
static class Instruction {
Operation op;
int num;
Instruction(Operation op, int num) {
this.op = op;
this.num = num;
}
public void execute(VirtualMachine c) {
op.execution.accept(c, num);
}
public static Instruction parse(String instr) {
Matcher m = INSTRUCTION_PATTERN.matcher(instr);
if (!m.matches()) throw new IllegalInputException("Invalid instruction format");
return new Instruction(
Operation.INDEX_OPCODE.valueOf(m.group(1)),
Integer.parseInt(m.group(2)));
}
}
enum Operation {
NO_OP ("nop", (vm, i) -> {}),
JUMP ("jmp", (vm, i) -> vm.instrIndex += (i - 1)), // Minus 1 to negate the default index increment
ACCUMULATOR ("acc", (vm, i) -> vm.accumulator += i);
public static final EnumIndex<Operation, String> INDEX_OPCODE =
new EnumIndex<>(Operation.class, e -> e.opcode);
final String opcode;
final BiConsumer<VirtualMachine, Integer> execution;
Operation(String opcode, BiConsumer<VirtualMachine, Integer> execution) {
this.opcode = opcode;
this.execution = execution;
}
}
}