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