1+ #!/usr/bin/env python
2+ # -*- coding: utf-8 -*-
3+
4+ def parse (data ):
5+ lines = data .split ('\n ' )
6+ register = {
7+ 'A' : int (lines [0 ].split (': ' )[1 ]),
8+ 'B' : int (lines [1 ].split (': ' )[1 ]),
9+ 'C' : int (lines [2 ].split (': ' )[1 ]),
10+ }
11+ instructions = lines [4 ].split (': ' )[1 ].split (',' )
12+ return register , [int (x ) for x in instructions ]
13+
14+ split_data = parse
15+ completed = True
16+ raw_data = None # Not To be touched
17+
18+ def part1 (data ):
19+ register , instructions = data
20+
21+ def combo (oprand ):
22+ if 0 <= oprand <= 3 :
23+ return oprand
24+ elif oprand == 4 :
25+ return register ['A' ]
26+ elif oprand == 5 :
27+ return register ['B' ]
28+ elif oprand == 6 :
29+ return register ['C' ]
30+ else :
31+ raise "How are we executing a reserved value?"
32+
33+ pointer = 0
34+ output = []
35+ while pointer < len (instructions ):
36+ opcode = instructions [pointer ]
37+ oprand = instructions [pointer + 1 ]
38+
39+ match opcode :
40+ case 0 :
41+ register ['A' ] = int (register ['A' ] / (2 ** combo (oprand )))
42+ pointer += 2
43+ case 1 :
44+ register ['B' ] = register ['B' ] ^ oprand
45+ pointer += 2
46+ case 2 :
47+ register ['B' ] = combo (oprand ) % 8
48+ pointer += 2
49+ case 3 :
50+ if register ['A' ] == 0 :
51+ pointer += 2
52+ else :
53+ pointer = oprand
54+ case 4 :
55+ register ['B' ] = register ['B' ] ^ register ['C' ]
56+ pointer += 2
57+ case 5 :
58+ output .append (str (combo (oprand ) % 8 ))
59+ pointer += 2
60+ case 6 :
61+ register ['B' ] = int (register ['A' ] / (2 ** combo (oprand )))
62+ pointer += 2
63+ case 7 :
64+ register ['C' ] = int (register ['A' ] / (2 ** combo (oprand )))
65+ pointer += 2
66+ case default :
67+ raise "ERR"
68+
69+ # print(pointer, output, register)
70+ return ',' .join (output )
71+
72+
73+ def compute (register , instructions ):
74+ pointer = 0
75+ output = []
76+ while pointer < len (instructions ):
77+ opcode = instructions [pointer ]
78+ oprand = instructions [pointer + 1 ]
79+ combo = ({4 :register ['A' ],5 :register ['B' ],6 :register ['C' ]}).get (oprand , oprand )
80+
81+ match opcode :
82+ case 0 :
83+ register ['A' ] = int (register ['A' ] / (2 ** combo ))
84+ pointer += 2
85+ case 1 :
86+ register ['B' ] = register ['B' ] ^ oprand
87+ pointer += 2
88+ case 2 :
89+ register ['B' ] = combo % 8
90+ pointer += 2
91+ case 3 :
92+ if register ['A' ] == 0 :
93+ pointer += 2
94+ else :
95+ pointer = oprand
96+ case 4 :
97+ register ['B' ] = register ['B' ] ^ register ['C' ]
98+ pointer += 2
99+ case 5 :
100+ output .append (combo % 8 )
101+ if len (output ) > len (instructions ) or output [- 1 ] != instructions [len (output )- 1 ]: return False
102+ pointer += 2
103+ case 6 :
104+ register ['B' ] = int (register ['A' ] / (2 ** combo ))
105+ pointer += 2
106+ case 7 :
107+ register ['C' ] = int (register ['A' ] / (2 ** combo ))
108+ pointer += 2
109+
110+ return output == instructions
111+
112+ def part2 (data ):
113+ """
114+ Forget about getting the answer by brute forcing it.
115+ It's not going to happen as the values we are looking for are too large
116+ A much simpler method is backtracking. We can look at our program and notice a few things
117+
118+ My program:
119+
120+ B = A mod 8
121+ B = B XOR 7
122+ C = A / 2**B
123+ A = A / 8
124+ B = B XOR 7
125+ B = B XOR C
126+ OUT (B mod 8)
127+ if A != 0 then jump 0
128+
129+
130+ It ends with 3,0 aka if A != 0 then jump 0
131+ and the only modification done to A is A = A / 2**3
132+
133+ Thus we know that our A should end in 0 for the program to halt which leaves us with a range of for A at the start [0*8, 1*8)
134+ The range equals out to 0,1,2,3,4,5,6,7. `[]` brackets means inclusive and `()` brackets mean exclusive.
135+
136+ Running these possibilities through the program we can see that only 0 and 7 output a `0`.
137+ As A can't with a 0 (as it would cause the program to halt in the previous cycle) we know the program will have to have a 7 at the start.
138+ Thus in the last cycle we begin with a 7 to allow the program to halt and also give us the required `0` as output
139+ Now we can extend the same logic backwards. Now we check the [7*8, (7+1)*8) interval for a 3 as output giving us 58 and 60.
140+ Thus creating the interval [58*8,(58+1)*8) and [60*8, (60+1)*8) to check for the third last value and so on...
141+
142+ Summary:
143+ last value: [0*8, 1*8) interval that outputs 0
144+ 2nd last value: [7*8, (7+1)*8) interval that outputs 3
145+ 3rd last value: [58*8,(58+1)*8) and [60*8, (60+1)*8) interval that outputs x
146+ and so on...
147+
148+ As we are checking at max 8n possibilities for a single cycle where n averages at ~8 we should not have to look for much
149+
150+ Thus we get values of A which all end up producing the output we want. This may only work my input
151+ """
152+
153+ _ , program = data
154+
155+
156+ def output (A ):
157+ return ((A % 8 )^ 7 ^ 7 ^ int (A / 2 ** ((A % 8 ) ^ 7 ))) % 8 # Simplified version
158+ print (A )
159+ B = A % 8
160+ B = A ^ 7
161+ C = int (A / 2 ** B )
162+ A = int (A / 8 )
163+ B = B ^ 7
164+ B = B ^ C
165+ return B % 8
166+
167+ growth = [0 ]
168+ n = []
169+ for out in program [::- 1 ]:
170+ growth = [x for a in growth for x in range (a * 8 , (a + 1 )* 8 ) if x != 0 and output (x ) == out ]
171+ n .append (len (growth ))
172+ return growth [0 ]
173+
174+ """
175+ Program: 2,4 1,7 7,5 0,3 1,7 4,1 5,5 3,0
176+
177+ B = A mod 8
178+ B = B XOR 7 (7,6,5,4,3,2,1,0 as possibilities)
179+ C = A / 2**B
180+ A = A / 8
181+ B = B XOR 7 (0,1,2,3,4,5,6,7 as possibilities)
182+ B = B XOR C
183+ OUT B mod 8
184+ if A != 0 then jump 0
185+
186+ ```
187+ def check(A):
188+ return ((A % 8) ^ int(A / 2**((A % 8) ^ 7))) % 8
189+
190+ growth = [0]
191+ n = []
192+ for output in (2,4,1,7,7,5,0,3,1,7,4,1,5,5,3,0)[::-1]:
193+ growth = [x for a in growth for x in range(a*8, (a+1)*8) if x != 0 and check(x) == output]
194+ n.append(len(growth))
195+
196+ growth[0]
197+ ```
198+
199+
200+
201+ The output is being done by B essentially.
202+ We can manually back track the entire fucking thing...
203+
204+ The only way to terminate the program is if A started less than 8
205+ We get 0 as output if A was 0 or 7 at the start.
206+
207+ Knowing the logic of the program. We check [0*8, 1*8) and [7*8, 8*8) for output of 3.
208+ Sure enough the outputs 3, 58, and 60 all produce 3 which go on to produce 0.
209+ Thus we check for [3*8,4*8), [58*8, 59*8), and [60*8, 61*8) for production of 5.
210+ and so on and we are bound to find the value...
211+
212+ Note: 0 can't be a possibility since it would mean that mean it would have terminated before...
213+
214+
215+
216+
217+
218+ Thus the next values we look
219+
220+ Hence A started out with <8
221+ B = 7 - A
222+ C = A / 2**(7-A) (0,1,3,7 as possible values)
223+ B = A
224+
225+
226+ """
0 commit comments