zerosleeps

Since 2010

Advent of Code 2020 day 8

Thursday 10 December 2020

Advent of Code 2020 day 8. Happy with the logic, but not particularly happy with the implementation: lots of while loops and if statements that all look the same when you squint at the code.

Rather than reuse the same Console object for each iteration in part two and reset it’s state, I wish I’d created a new object for each iteration. But this works and I’m behind, so on to day 9.

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
from pathlib import Path
import unittest

def get_raw_input():
    return (Path(__file__).parent/'day_08_input.txt').read_text()

class Console():
    def __init__(self, raw_input):
        self.instructions = self.parse_raw_input(raw_input)
        self.accumulator = 0
        self.next_instruction = 0
        self.instruction_log = []
        self.mutate_line = -1

    @staticmethod
    def parse_raw_input(raw_input):
        return [
            (line.strip().split()[0], int(line.strip().split()[1]))
            for line
            in raw_input.strip().splitlines()
        ]

    def reset_state(self):
        self.accumulator = 0
        self.next_instruction = 0
        self.instruction_log = []

    def run(self):
        while self.next_instruction not in self.instruction_log:
            try:
                current_instruction = self.instructions[self.next_instruction]
            except IndexError:
                return (self.accumulator, 0)

            self.instruction_log.append(self.next_instruction)
            self.next_instruction += 1

            if current_instruction[0] == 'acc':
                self.accumulator += int(current_instruction[1])
            elif current_instruction[0] == 'jmp':
                self.next_instruction += (int(current_instruction[1]) - 1)
            elif current_instruction[0] == 'nop':
                pass

        return self.accumulator

    def mutate_input(self):

        last_line_mutated = self.mutate_line

        if last_line_mutated >= 0:
            # Revert previous mutation
            if self.instructions[last_line_mutated][0] == 'nop':
                self.instructions[last_line_mutated] = ('jmp', self.instructions[last_line_mutated][1])
            else:
                self.instructions[last_line_mutated] = ('nop', self.instructions[last_line_mutated][1])

            self.reset_state()

        self.mutate_line += 1

        while self.instructions[self.mutate_line][0] not in ['nop', 'jmp']:
            self.mutate_line += 1

        if self.instructions[self.mutate_line][0] == 'nop':
            self.instructions[self.mutate_line] = ('jmp', self.instructions[self.mutate_line][1])
        else:
            self.instructions[self.mutate_line] = ('nop', self.instructions[self.mutate_line][1])
        return True

def part_one(raw_input):
    return Console(raw_input).run()

def part_two(raw_input):
    c = Console(raw_input)
    while type(c.run()) != tuple:
        c.mutate_input()
    return c.accumulator

class TestExamples(unittest.TestCase):
    def setUp(self):
        self.example_input = """nop +0
            acc +1
            jmp +4
            acc +3
            jmp -3
            acc -99
            acc +1
            jmp -4
            acc +6"""

    def test_part_one(self):
        self.assertEqual(part_one(self.example_input), 5)

    def test_part_one(self):
        self.assertEqual(part_two(self.example_input), 8)

if __name__ == '__main__':
    print(f'Part one: {part_one(get_raw_input())}')
    print(f'Part two: {part_two(get_raw_input())}')