-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtask_02.py
112 lines (94 loc) · 4.06 KB
/
task_02.py
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
# Standard Library
import math
import operator
class Task02:
@classmethod
def solve(cls, file_content: list[str]) -> int:
program = []
for line in file_content:
# We only care about the program this time round
if line.startswith("Program"):
parts = line.strip().split(" ")
program = [int(value) for value in parts[len(parts) - 1].split(",")]
break
result = cls.find_quine(program)
return result
@classmethod
def find_quine(cls, program: list[int]) -> int:
"""
The output is the program itself
https://en.wikipedia.org/wiki/Quine_(computing)
There are no shortcuts for brute forcing this, to generate a 16 digit output means that register A needs to be in the range 35184372088832 to
281474976710655, this means 246,290,604,621,823 possible iterations :(
This problem would never have been done without the excellent video by Wekoslav Stefanovski: https://youtu.be/QpvAyg1RIYI?si=Adiw2hbi8DNvVE-5.
The video provides a good explanation of the solution and it also reasonably easy to follow.
"""
register_a = 0
for index in reversed(range(len(program))):
register_a <<= 3
registers = {}
registers["A"] = register_a
registers["B"] = 0
registers["C"] = 0
output, _ = cls.run_program(registers=registers, program=program)
while output != program[index:]:
register_a = register_a + 1
registers = {}
registers["A"] = register_a
registers["B"] = 0
registers["C"] = 0
output, _ = cls.run_program(registers=registers, program=program)
return register_a
@classmethod
def get_combo_operand(cls, operand: int, registers: dict[str, int]) -> int:
if operand >= 0 and operand <= 3:
return operand
if operand == 4:
return registers["A"]
if operand == 5:
return registers["B"]
if operand == 6:
return registers["C"]
raise ValueError("Expected operand in the range 0-6, received {operand}".format(operand=operand))
@classmethod
def run_program(cls, registers: dict[str, int], program: list[int]) -> tuple[list[int], dict[str, int]]:
output: list[int] = []
pointer = 0
run_registers = registers.copy()
while pointer < len(program):
opcode = program[pointer]
operand = program[pointer + 1]
if opcode == 0:
# adv
numerator = run_registers["A"]
denominator = 2 ** cls.get_combo_operand(operand, run_registers)
run_registers["A"] = math.trunc(numerator / denominator)
if opcode == 1:
# bxl
run_registers["B"] = operator.xor(operand, run_registers["B"])
if opcode == 2:
# bst
run_registers["B"] = cls.get_combo_operand(operand, run_registers) % 8
if opcode == 3:
# jnz
if run_registers["A"] != 0:
pointer = operand
continue
if opcode == 4:
# bxc
run_registers["B"] = operator.xor(run_registers["C"], run_registers["B"])
if opcode == 5:
# out
output.append(cls.get_combo_operand(operand, run_registers) % 8)
if opcode == 6:
# bdv
numerator = run_registers["A"]
denominator = 2 ** cls.get_combo_operand(operand, run_registers)
run_registers["B"] = math.trunc(numerator / denominator)
if opcode == 7:
# cdv
numerator = run_registers["A"]
denominator = 2 ** cls.get_combo_operand(operand, run_registers)
run_registers["C"] = math.trunc(numerator / denominator)
pointer = pointer + 2
return (output, run_registers)