Skip to content

Commit 8faa6a4

Browse files
committed
Solve python/2024/17 (rough)
1 parent 110bd54 commit 8faa6a4

File tree

2 files changed

+244
-0
lines changed

2 files changed

+244
-0
lines changed

2024/d17.py

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
#!/bin/python
2+
"""Advent of Code, Day 17: Chronospatial Computer."""
3+
from __future__ import annotations
4+
5+
import collections
6+
import functools
7+
import itertools
8+
import math
9+
import random
10+
import re
11+
12+
from lib import aoc
13+
14+
SAMPLE = [
15+
"""\
16+
Register A: 729
17+
Register B: 0
18+
Register C: 0
19+
20+
Program: 0,1,5,4,3,0""", # 76
21+
"""\
22+
Register A: 2024
23+
Register B: 0
24+
Register C: 0
25+
26+
Program: 0,3,5,4,3,0"""
27+
]
28+
29+
LineType = int
30+
InputType = list[LineType]
31+
32+
33+
class Day17(aoc.Challenge):
34+
"""Day 17: Chronospatial Computer."""
35+
36+
DEBUG = True
37+
# Default is True. On live solve, submit one tests pass.
38+
# SUBMIT = {1: False, 2: False}
39+
40+
TESTS = [
41+
aoc.TestCase(part=1, inputs=SAMPLE[0], want="4,6,3,5,6,3,5,2,1,0"),
42+
# aoc.TestCase(part=2, inputs=SAMPLE[1], want=117440),
43+
aoc.TestCase(part=2, inputs=SAMPLE[0], want=aoc.TEST_SKIP),
44+
]
45+
46+
INPUT_PARSER = aoc.ParseBlocks([aoc.parse_ints, aoc.parse_re_findall_int(r"\d+")])
47+
48+
def simulate(self, reg, instructions) -> list[int]:
49+
ptr = 0
50+
51+
def val(i, combo):
52+
if not combo:
53+
return i
54+
if 0 <= i <= 3:
55+
return i
56+
return reg[{4: "a", 5: "b", 6: "c"}[i]]
57+
58+
out = []
59+
# print(ptr, len(instructions))
60+
while ptr < len(instructions) - 1:
61+
instruction, op = instructions[ptr], instructions[ptr + 1]
62+
ptr += 2
63+
# print("a", "_".join("".join(i) for i in itertools.batched(bin(reg["a"])[2:], n=3)))
64+
65+
if instruction == 0: # adv
66+
reg["a"] = reg["a"] // (2 ** val(op, True))
67+
if instruction == 1: # bxl
68+
reg["b"] = reg["b"] ^ op
69+
if instruction == 2: # bst
70+
reg["b"] = val(op, True) % 8
71+
if instruction == 3: # jnz
72+
if reg["a"] != 0:
73+
ptr = op
74+
if instruction == 4: # bxc
75+
reg["b"] = reg["b"] ^ reg["c"]
76+
if instruction == 5: # out
77+
# print(f"{ptr=}, {op=}, {val(op, True)}")
78+
# print("a", "_".join("".join(i) for i in itertools.batched(bin(reg["a"])[2:], n=3)))
79+
# print("Output", (val(op, True) % 8))
80+
out.append((val(op, True) % 8))
81+
if instruction == 6: # bdv
82+
reg["b"] = reg["a"] // (2 ** val(op, True))
83+
if instruction == 7: # cdv
84+
reg["c"] = reg["a"] // (2 ** val(op, True))
85+
# print(f"cdv dist = {reg['b']}, val = {reg['c'] % 8=}, inverted = {7 - (reg['c'] % 8)}",)
86+
# print(instruction, op)
87+
# for i in "abc":
88+
# print(i, reg[i] % 8, bin(reg[i] % 8))
89+
90+
return out
91+
92+
def part1(self, puzzle_input: InputType) -> int:
93+
reg = {char: puzzle_input[0][i][0] for i, char in enumerate("abc")}
94+
instructions = puzzle_input[1][0]
95+
return ",".join(str(i) for i in self.simulate(reg, instructions))
96+
97+
98+
def part2(self, puzzle_input: InputType) -> int:
99+
100+
instructions = puzzle_input[1][0]
101+
102+
def simulate(reg_a: int) -> list[int]:
103+
out = []
104+
while reg_a:
105+
shift = reg_a & 7 ^ 7
106+
digit = (3 ^ reg_a ^ (reg_a >> shift)) & 7
107+
out.append(digit)
108+
reg_a = reg_a >> 3
109+
return out
110+
111+
test_val = 0b111_111_010_101
112+
assert self.simulate({"a": test_val, "b": 0, "c": 0}, instructions) == [3, 6, 3, 3]
113+
assert simulate(test_val) == [3, 6, 3, 3]
114+
115+
if True:
116+
mask = (1 << 13) - 8
117+
def solve(given, digits):
118+
# print(f"solve({given=}, {digits=})")
119+
given <<= 3
120+
options = set()
121+
for i, shift in itertools.product(range(8), repeat=2):
122+
shifted = (i << shift) & mask
123+
last_3 = shift ^ 7
124+
# digit = 3 ^ last_3 ^ (shifted >> shift)
125+
# if digit != instructions[-digits]:
126+
# continue
127+
new_num = given | shifted | last_3
128+
got = simulate(new_num)
129+
# print(got)
130+
if len(got) == digits and got == instructions[-len(got):]:
131+
options.add(new_num)
132+
if digits != 16:
133+
new_opts = set()
134+
for option in options:
135+
if (got := solve(option, digits + 1)) is not None:
136+
new_opts.add(got)
137+
options = new_opts
138+
if digits == 16:
139+
for option in options:
140+
if (got := simulate(option)) == instructions:
141+
print(f"{option} yields {got}")
142+
return min(options) if options else None
143+
144+
return solve(0, 1)
145+
146+
147+
148+
149+
150+
if False:
151+
for a in [0b111_111_010_101]:
152+
print("Test", a)
153+
print("Sim out", "".join(str(i) for i in self.simulate({"a": a, "b": 0, "c": 0}, instructions)))
154+
print("Run")
155+
print("a", "_".join("".join(i) for i in itertools.batched(bin(a)[2:], n=3)))
156+
out = ""
157+
while a:
158+
print("a", "_".join("".join(i) for i in itertools.batched(bin(a)[2:], n=3)))
159+
shift = a & 7 ^ 7
160+
# print("shift", b)
161+
digit = (3 ^ a ^ (a >> shift)) & 7
162+
out += str(digit)
163+
a = a >> 3
164+
print("Simple out", out)
165+
return
166+
167+
if False:
168+
want = instructions[:]
169+
want_str = "".join(str(i) for i in want)
170+
shifts = itertools.product(range(8), repeat=len(want))
171+
protected = 0
172+
for s in shifts:
173+
num = 0
174+
for digit, a_part in zip(reversed(want), s):
175+
num <<= 3
176+
protected <<= 3
177+
do_not_touch = num & protected
178+
179+
shift = a_part ^ 7
180+
shift_part = digit ^ 3 ^ a
181+
# shift_part = (shift_part << shift) ^
182+
183+
184+
185+
186+
neg_dist = dist ^ 7
187+
num |= neg_dist | (digit ^ 7) << dist
188+
if num & 7 != neg_dist:
189+
break
190+
if num & (7 << dist) != (digit ^ 7):
191+
break
192+
if do_not_touch != num & protected:
193+
break
194+
protected |= 7 | 7 << dist
195+
else:
196+
print(num)
197+
a = num
198+
out = ""
199+
while a:
200+
bits = a & 7
201+
b = bits ^ 7
202+
out += str(7 ^ ((a >> b) & 7))
203+
a = a >> 3
204+
if out == want_str:
205+
assert num < 266582694363549
206+
return num
207+
208+
return
209+
210+
211+
else:
212+
for i in itertools.count():
213+
if not i % 1000000:
214+
print(i)
215+
a = i
216+
outcount = 0
217+
while a:
218+
b = (a % 8) ^ 7
219+
c = a >> b
220+
b ^= c
221+
b ^= a
222+
out = b % 8
223+
if out == want[outcount]:
224+
outcount += 1
225+
if outcount == len_want:
226+
return i
227+
else:
228+
break
229+
a //= 8
230+
231+
reg = {char: puzzle_input[0][i][0] for i, char in enumerate("abc")}
232+
instructions = puzzle_input[1][0]
233+
want = [str(i) for i in instructions]
234+
235+
for i in itertools.count():
236+
reg["a"] = i
237+
got = self.simulate(reg.copy(), instructions)
238+
if got == instructions:
239+
return i
240+
if got[:-4] == [0,3,3,0]:
241+
print(i, got)
242+
243+
# vim:expandtab:sw=4:ts=4

go/aoc.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ var puzzles = map[helpers.Puzzle]helpers.Solver{
2525
helpers.Puzzle{2024, 1}: y2024.New01(),
2626
helpers.Puzzle{2024, 2}: y2024.New02(),
2727
helpers.Puzzle{2024, 3}: y2024.New03(),
28+
helpers.Puzzle{2024, 17}: y2024.New17(),
2829
}
2930

3031
func main() {

0 commit comments

Comments
 (0)