Skip to content

Commit e9c2a83

Browse files
committed
Add day 24, 2024
1 parent 51354d9 commit e9c2a83

File tree

1 file changed

+156
-0
lines changed

1 file changed

+156
-0
lines changed

2024/day24/solution.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import networkx as nx
2+
import random
3+
4+
with open("input") as f:
5+
inp1, inp2 = f.read().strip().split("\n\n")
6+
7+
conns = {}
8+
for line in inp2.split("\n"):
9+
x, op, y, _, z = line.split(" ")
10+
conns[z] = (x, y, op)
11+
12+
xy_vals = inp1.split("\n")
13+
14+
x = ""
15+
for i in range(45):
16+
x += xy_vals[i].split(": ")[1]
17+
x = x[::-1]
18+
19+
y = ""
20+
for i in range(45, 90):
21+
y += xy_vals[i].split(": ")[1]
22+
y = y[::-1]
23+
24+
25+
def process(x, y, conns):
26+
n = len(conns.keys()) + 2*45
27+
ops = {"OR": "|", "AND": "&", "XOR": "^"}
28+
vals = {}
29+
30+
for i, bit in enumerate(reversed(x)):
31+
vals[f"x{i:02}"] = int(bit)
32+
33+
for i, bit in enumerate(reversed(y)):
34+
vals[f"y{i:02}"] = int(bit)
35+
36+
while len(vals.keys()) < n:
37+
cur_len = len(vals.keys())
38+
for z, (x, y, op) in conns.items():
39+
if x not in vals.keys() or y not in vals.keys():
40+
continue
41+
vals[z] = eval(f"{vals[x]} {ops[op]} {vals[y]}")
42+
if len(vals.keys()) == cur_len:
43+
return None
44+
45+
zs = sorted([(wire, val) for wire, val in vals.items() if wire.startswith("z")], reverse = True)
46+
ans = "".join([str(val) for _, val in zs])
47+
return ans
48+
49+
# Part 1
50+
print(int(process(x, y, conns), 2))
51+
52+
# Part 2
53+
def xbit(i):
54+
return "0" * (45 - i - 1) + "1" + "0" * i
55+
56+
# Looking at individual bits, we see that z06/z07, z11, z24, z35/z36 are problematic.
57+
for i in range(45):
58+
x = xbit(i)
59+
print(process(x, x, conns), f"z{i+1}")
60+
61+
62+
g = nx.Graph()
63+
for line in inp2.split("\n"):
64+
x, op, y, _, z = line.split(" ")
65+
g.add_edge(x, z)
66+
g.add_edge(y, z)
67+
68+
69+
def works(i, conns):
70+
x = xbit(i)
71+
return process(x, x, conns) == "0" + xbit(i + 1)
72+
73+
74+
def works_for_all(conns):
75+
for i in range(44):
76+
if not works(i, conns):
77+
return False
78+
return True
79+
80+
81+
def get_neighbors(node, depth):
82+
if depth == 1:
83+
return set(g.neighbors(node))
84+
res = set(g.neighbors(node))
85+
for subnode in set(g.neighbors(node)):
86+
res |= get_neighbors(subnode, depth - 1)
87+
return res
88+
89+
90+
def swap(node1, node2, conns):
91+
new_conns = dict(conns)
92+
new_conns[node1], new_conns[node2] = new_conns[node2], new_conns[node1]
93+
return new_conns
94+
95+
96+
# For each problematic bit we look at any two neighbors (of depth 3) and see if swapping them would resolve the issue.
97+
# For each problematic bit there may be more than one swap that does the job, so we store them all.
98+
swaps1 = []
99+
from itertools import combinations
100+
for node1, node2 in combinations(get_neighbors("z06", 3), 2):
101+
if node1[0] in ["x", "y"] or node2[0] in ["x", "y"]:
102+
continue
103+
if works(5, swap(node1, node2, conns)):
104+
swaps1.append((node1, node2))
105+
106+
swaps2 = []
107+
for node1, node2 in combinations(get_neighbors("z11", 3), 2):
108+
if node1[0] in ["x", "y"] or node2[0] in ["x", "y"]:
109+
continue
110+
if works(10, swap(node1, node2, conns)):
111+
swaps2.append((node1, node2))
112+
113+
swaps3 = []
114+
for node1, node2 in combinations(get_neighbors("z24", 3), 2):
115+
if node1[0] in ["x", "y"] or node2[0] in ["x", "y"]:
116+
continue
117+
if works(23, swap(node1, node2, conns)):
118+
swaps3.append((node1, node2))
119+
120+
swaps4 = []
121+
for node1, node2 in combinations(get_neighbors("z35", 3), 2):
122+
if node1[0] in ["x", "y"] or node2[0] in ["x", "y"]:
123+
continue
124+
if works(34, swap(node1, node2, conns)):
125+
swaps4.append((node1, node2))
126+
127+
128+
# We now create a map of all the (so far) valid swaps and their associated connections.
129+
valid_swaps = {}
130+
for swap1 in swaps1:
131+
for swap2 in swaps2:
132+
for swap3 in swaps3:
133+
for swap4 in swaps4:
134+
conns_alt = dict(conns)
135+
conns_alt = swap(swap1[0], swap1[1], conns_alt)
136+
conns_alt = swap(swap2[0], swap2[1], conns_alt)
137+
conns_alt = swap(swap3[0], swap3[1], conns_alt)
138+
conns_alt = swap(swap4[0], swap4[1], conns_alt)
139+
if works_for_all(conns_alt):
140+
valid_swaps[(swap1, swap2, swap3, swap4)] = conns_alt
141+
142+
143+
# We now randomly perform additions to weed out all the faulty swaps until we only have
144+
# one remaining set of swaps.
145+
while True:
146+
num1 = bin(random.getrandbits(45))[2:]
147+
num1 = num1.zfill(45)
148+
num2 = bin(random.getrandbits(45))[2:]
149+
num2 = num2.zfill(45)
150+
for swaps, conns_alt in list(valid_swaps.items()):
151+
if int(process(num1, num2, conns_alt), 2) != int(num1, 2) + int(num2, 2):
152+
valid_swaps.pop(swaps)
153+
if len(valid_swaps.keys()) == 1:
154+
break
155+
156+
print(",".join(sorted([node for tple in list(valid_swaps.keys())[0] for node in tple])))

0 commit comments

Comments
 (0)