~schnouki/advent-of-code

f0ce29b9824e6a3b81af0cfc67965fdc7edbc656 — Thomas Jost 2 months ago 50a94dc
Day 8
2 files changed, 828 insertions(+), 0 deletions(-)

A 2020/day08.py
A 2020/inputs/day08
A 2020/day08.py => 2020/day08.py +168 -0
@@ 0,0 1,168 @@
#!/usr/bin/env python3

from dataclasses import dataclass, field
from typing import Any, Callable, Optional

from aoc import Puzzle, run


@dataclass
class VM:
    ops: list[tuple[str, int]]
    ip: int = field(default=0)  # instruction pointer
    stopped: bool = field(default=False)

    acc: int = field(default=0)  # accumulator

    callback: Optional[Callable[["VM"], Any]] = field(default=None)

    @classmethod
    def from_string(cls, data: str) -> "VM":
        ops: list[tuple[str, int]] = []

        for line in data.splitlines():
            line = line.strip()
            op, arg = line.split()
            ops.append((op, int(arg)))

        return cls(ops=ops)

    def clone(self) -> "VM":
        return VM(ops=self.ops.copy())

    def reset(self):
        self.ip = 0
        self.stopped = False
        self.acc = 0
        self.callback = None

    def step(self):
        """Run one instruction."""
        if self.callback:
            self.callback(self)
        if self.stopped:
            return

        op, arg = self.ops[self.ip]
        func = getattr(self, f"op_{op}")
        keep_ip = func(arg)
        if not keep_ip:
            self.ip += 1

    def run(self):
        """Run the program until stopped."""
        while 0 <= self.ip < len(self.ops) and not self.stopped:
            self.step()

    def op_nop(self, arg):
        pass

    def op_acc(self, arg: int):
        self.acc += arg

    def op_jmp(self, arg: int):
        self.ip += arg
        return True


class Day08(Puzzle):
    test_data = [
        """nop +0
acc +1
jmp +4
acc +3
jmp -3
acc -99
acc +1
jmp -4
acc +6"""
    ]
    test_result_part1 = [5]
    test_result_part2 = [8]

    def prepare_data(self, data: str) -> VM:
        return VM.from_string(data)

    def run_part1(self, data: VM):
        visited_ips = set()
        result = None

        def check_visited_ips(vm):
            nonlocal result
            if vm.ip in visited_ips:
                vm.stopped = True
                result = vm.acc
            else:
                visited_ips.add(vm.ip)

        vm = data.clone()
        vm.callback = check_visited_ips

        vm.run()
        return result

    def run_part2(self, data):
        visited_ips = set()
        prev_fixes = set()
        fixed = False

        def fix_corruption(vm):
            nonlocal fixed
            ip = vm.ip
            looping = False

            if ip in visited_ips:
                looping = True
                if fixed:
                    # Try again.
                    vm.stopped = True

            visited_ips.add(ip)

            op, arg = vm.ops[ip]
            if op == "acc":
                return

            if looping:
                # What would happen if we changed the current op?
                new_ip, alt_ip, alt_op = -1, -1, ""
                if op == "nop":
                    new_ip = ip + 1
                    alt_ip = ip + arg
                    alt_op = "jmp"
                else:
                    new_ip = ip + arg
                    alt_ip = ip + 1
                    alt_op = "nop"

                # Would flipping this op get us out of the loop?
                if (
                    new_ip in visited_ips
                    and alt_ip not in visited_ips
                    and ip not in prev_fixes
                ):
                    # Bingo!
                    # print(f"{ip}: {op} {arg} --> {alt_op} {arg}")
                    vm.ops[ip] = (alt_op, arg)
                    fixed = True
                    prev_fixes.add(ip)

        vm = data.clone()
        vm.stopped = True
        while vm.stopped:
            visited_ips.clear()
            fixed = False

            vm = data.clone()
            vm.callback = fix_corruption
            vm.run()

        # Not stopped: that fix worked! Now let's run it uncorrupted.
        vm.reset()
        vm.run()

        return vm.acc


if __name__ == "__main__":
    run(obj=Day08())

A 2020/inputs/day08 => 2020/inputs/day08 +660 -0
@@ 0,0 1,660 @@
jmp +11
nop +495
nop +402
jmp +570
jmp +569
jmp +451
acc -12
jmp +364
acc +30
acc +21
jmp +430
jmp +87
acc -12
acc -4
acc +11
jmp +50
jmp +149
jmp +341
nop +1
acc +33
jmp +461
acc +43
acc -15
jmp +440
acc +18
acc +22
acc -14
nop +576
jmp -22
acc +33
acc +0
acc +34
jmp +369
nop +497
nop +469
acc -12
jmp +93
acc -13
jmp +337
acc +5
acc +18
acc +26
nop +115
jmp +67
nop +282
nop -6
nop +289
jmp +303
acc +10
acc -15
jmp +153
nop +445
acc -8
acc +11
jmp +374
acc +35
acc -9
nop +199
jmp +1
jmp +369
jmp +1
acc +22
acc -18
acc +17
jmp +303
acc +37
acc +13
acc +22
nop +307
jmp +154
nop +381
acc -6
nop -54
acc +38
jmp +494
acc +37
acc +15
jmp +475
jmp +474
nop +534
acc +37
jmp +359
jmp +296
acc +40
jmp +157
acc +5
nop +139
acc +49
acc +45
jmp +237
acc +42
acc +8
acc +43
jmp +466
nop +362
acc +43
acc +44
jmp +233
acc +30
acc +42
acc -6
jmp -97
jmp +527
acc +2
acc +13
nop +425
jmp +113
nop +374
acc +31
jmp +48
acc +29
acc +15
acc +35
jmp +357
acc +19
acc -2
jmp +480
acc +1
jmp +385
acc +16
acc +47
jmp +397
jmp +1
jmp +1
acc +5
acc -5
jmp +529
acc +11
jmp +123
acc +44
acc +49
jmp +413
acc +13
acc +10
acc -6
acc +11
jmp -33
acc +25
acc +47
acc +40
acc +23
jmp +39
acc +50
acc +12
jmp +386
acc +23
jmp +464
acc +15
nop +361
acc +30
jmp +346
jmp +1
acc +19
acc +16
acc +23
jmp +441
jmp +69
acc +12
acc +46
acc -5
jmp +427
acc +49
nop +173
acc -12
jmp +249
acc +41
acc +29
nop +168
acc +31
jmp +349
acc +40
acc +8
acc +14
jmp +231
acc -17
jmp +215
nop +202
acc +0
nop +172
jmp +351
acc +21
acc +31
nop +405
acc +14
jmp +272
acc +5
acc +33
acc +31
acc +21
jmp -91
acc -18
acc +35
jmp +23
acc -10
nop +374
acc +27
jmp -157
acc +39
acc +8
acc +34
acc +34
jmp +333
jmp -51
acc -18
acc +26
jmp +377
acc -5
jmp +190
acc +45
jmp +1
acc +29
jmp +202
acc +25
acc +30
jmp +90
acc +49
nop +240
jmp +109
jmp +291
acc +9
jmp +348
acc +39
jmp +3
jmp +273
jmp -218
jmp +150
jmp +1
jmp +148
acc +9
acc +11
acc +20
acc +3
jmp -167
nop +223
jmp +275
nop +309
jmp +20
acc -18
acc -10
acc -2
jmp +173
acc +13
acc -17
nop +132
acc -6
jmp +240
acc +37
acc +4
acc +49
acc -16
jmp +171
jmp +239
nop +300
nop +311
acc -9
jmp -180
acc +32
acc +21
jmp +1
jmp -62
nop +141
acc +46
acc +25
nop -7
jmp +318
acc +3
jmp +185
nop +196
acc +16
acc +18
jmp -47
acc +39
acc +35
acc +21
acc +14
jmp +23
acc +20
acc +20
jmp +97
nop -71
acc +50
acc -11
jmp -145
nop -218
acc +42
acc +23
acc +35
jmp +183
nop +16
nop -206
acc +2
acc +11
jmp +129
acc +37
acc +41
acc +47
nop -280
jmp +93
acc -12
acc +31
jmp +10
acc +2
acc +29
jmp +64
acc -14
nop +308
jmp -251
acc +8
acc +10
jmp +259
acc +38
nop -131
acc +45
jmp +6
acc +18
acc +43
nop -218
nop -94
jmp +178
jmp +1
acc +27
acc +29
jmp +324
acc -12
acc +30
jmp +115
acc -1
jmp -224
jmp +1
jmp +205
acc +47
jmp -66
acc +21
acc +44
jmp +147
acc +2
acc +50
acc -14
acc +50
jmp -165
acc +33
acc +20
acc -5
acc +19
jmp -246
acc +26
acc +44
jmp -96
acc +22
jmp +127
nop +25
acc -14
acc -15
jmp -314
jmp +1
acc +11
acc +12
jmp +71
acc +0
acc +16
acc +26
nop +242
jmp -172
acc +8
acc +15
acc -4
jmp -78
acc +42
jmp +225
acc -7
jmp +243
jmp +242
acc +23
acc +21
jmp +54
acc +25
jmp -18
jmp -42
jmp +26
jmp -368
acc +29
acc +22
acc +1
acc +0
jmp +255
acc +39
jmp +1
nop -332
acc +22
jmp +92
acc +45
acc +29
jmp +12
jmp +1
acc +22
jmp +1
jmp -245
jmp -162
acc -4
acc -4
jmp +28
nop -4
jmp -386
jmp -28
acc -1
acc +25
nop -28
jmp -10
acc +9
acc +45
jmp +1
acc +13
jmp -171
acc +3
acc +19
acc -8
acc +11
jmp -266
acc -18
nop -380
jmp -155
acc +36
acc -13
acc +35
acc -16
jmp -414
nop -408
jmp +36
acc +5
jmp +101
acc -7
acc +17
jmp -204
acc -14
jmp -99
jmp +1
nop -165
acc +10
acc +13
jmp +46
acc -13
jmp -358
acc -7
acc -14
jmp -31
acc +21
acc -9
jmp -46
nop -220
acc -1
jmp -105
acc +42
acc -14
jmp -75
acc +6
jmp -34
nop -391
acc -11
jmp -123
nop -234
acc -9
acc +35
jmp -317
nop +150
acc +11
jmp +138
acc +30
acc -11
acc +31
jmp -134
acc +20
jmp -200
acc +13
acc +14
acc +25
jmp -310
acc +13
acc +18
acc -1
jmp +85
jmp +72
acc +1
jmp -78
acc -8
jmp -149
acc +13
jmp -438
acc +38
nop -25
jmp -264
acc +50
acc +47
nop -241
acc +31
jmp -419
jmp +57
nop -359
jmp -323
acc +48
acc +4
acc -12
acc +42
jmp -167
acc +50
acc -9
jmp -138
nop -171
acc +48
jmp -398
acc -8
acc +41
acc +21
jmp -508
acc +29
acc +41
acc +35
acc +25
jmp -388
jmp -439
acc +26
acc +19
acc +13
acc -16
jmp -165
nop -61
acc +16
acc +20
jmp -312
nop -19
jmp -101
acc +1
jmp -515
acc +19
jmp +96
jmp +1
acc +42
acc +34
acc +33
jmp +80
nop -314
acc +12
acc +36
nop -405
jmp -87
acc +16
nop -100
acc +11
acc +15
jmp -524
nop -369
acc +8
jmp -386
acc +32
jmp -77
acc -7
acc -16
acc +33
acc +30
jmp -213
acc -2
jmp -403
acc +35
nop -81
jmp -340
acc +7
acc +3
jmp -444
jmp -445
jmp -218
acc +39
acc -9
jmp +1
jmp -284
acc +43
acc +1
acc -12
nop -218
jmp -460
jmp -404
jmp +1
jmp -135
jmp -223
jmp +1
acc +30
acc +36
jmp -61
jmp -580
acc -8
jmp -79
acc +18
acc -1
acc +9
jmp -321
jmp -560
acc +2
jmp -182
acc +18
acc +29
acc +28
jmp -129
acc +10
nop -120
jmp +16
acc +23
acc +9
acc +36
acc +24
jmp -589
acc +21
jmp -419
nop -275
jmp -231
jmp -341
acc +33
acc +30
jmp -470
nop -337
jmp -389
jmp +5
acc -14
nop -27
acc +5
jmp -78
acc +40
acc +2
acc -5
nop -205
jmp -537
jmp -318
nop -404
acc +12
acc +0
acc -4
jmp -54
acc +8
acc +32
nop -357
acc +35
jmp -153
acc +42
acc +17
acc -9
jmp -13
nop -222
acc +27
jmp -63
acc +11
acc -17
nop -45
acc +4
jmp -217
acc +5
acc +26
nop -574
jmp -489
acc +29
acc +36
acc +31
nop -407
jmp +1