Advent of Code 2020 - Day 8
Day 8 of AoC 2020 (Handheld Halting) is a nice instruction simulation problem, and fairly easy to boot. Spoilers ahead.
The input consists of a series of instructions, one instruction per line. There
are only 3 instructions - acc, jmp and nop. All instructions are followed
by a number; acc updates the internal accumulator by that number, jmp
updates the program counter by that number, while nop does nothing. The
program counter is incremented after acc and nop instructions.
One observation we can make is that a program with no corruption will have no
loops. This is because there are no conditional instructions, which is a
prerequisite for creating loops with an exit condition. A correct program will
always terminate, because all jmp instructions will jump to an instruction
that has not been executed before.
Let’s first tackle parsing the input. We can handle this by just splitting each line at whitespace, and converting the second field into an integer.
def load_data(datafile):
data = []
with open(datafile) as df:
for line in df:
instruction, value = line.strip().split()
value = int(value)
data.append([instruction, value])
return dataFor part 1, we need to simulate the instructions, until we find an instruction that has already been executed. We can keep track of this by creating a list that maps the program counter to a boolean value that indicates whether this PC has been visited yet, and updating it on execution.
def simulate(program):
pc = 0
acc = 0
visited = [False] * len(program)
while 0 <= pc < len(program) and not visited[pc]:
inst = program[pc]
visited[pc] = True
if inst[0] == 'acc':
acc += inst[1]
elif inst[0] == 'jmp':
pc += inst[1]
continue # So we don't increment the PC a second time.
elif inst[0] == 'nop':
# nop
pass
else:
print('Unknown instruction {} at {}'.format(inst, pc))
pc += 1
# If the program has terminated normally, pc should be outside the
# range of the input program. If it is within the program, then the
# simulator has encountered a loop.
return not 0 <= pc < len(program), accThe above code snippet is sufficient to get the result of the program right before we hit the infinite loop.
Part 2 asks us to find the one instruction that has been corrupted, i.e., find
either the nop that should have been a jmp, or the jmp that should have
been a nop. One way we can do this is to create a copy of the program, swap
the instruction (if it is not acc), and simulate the new program using our
simulate function in the above snippet.
This is essentially a brute force approach, but it is sufficient for our needs,
as the input is fairly small at under 700 instructions, and acc instructions
account for about half of that. Therefore, even a brute force solution would be
fairly quick.
swap = {'nop': 'jmp', 'jmp': 'nop'}
for pc, inst in enumerate(orig_program):
if inst[0] in swap:
new_program = copy.deepcopy(orig_program)
new_program[pc][0] = swap[inst[0]]
result, acc = simulate(new_program)
if result:
print('Part 2:', acc)
break