1
1
from aoc import *
2
- from itertools import pairwise , combinations , repeat , product , accumulate , permutations , cycle , combinations_with_replacement
3
- from more_itertools import first_true , flatten , ncycles , first_true , zip_broadcast , windowed , chunked , take , peekable
4
2
import re
5
- from blessed import BlessedList
6
- from collections import namedtuple , defaultdict , OrderedDict , Counter , deque , UserList
7
- import operator
8
- from operator import itemgetter
9
- from bisect import *
10
- from functools import reduce , cache , cmp_to_key
11
- import math
12
- from copy import deepcopy
13
- from math import ceil
14
- from heapq import *
15
- import sys
16
- from pprint import pprint as pp
17
- from statistics import mode
18
- from string import ascii_uppercase
19
- import random
3
+ from more_itertools import flatten
20
4
21
- def incorrect_z (s : int , n : int ) -> [str ]:
22
- i = 0
23
- incorrect = []
24
- while s or n :
25
- a = s % 2
26
- b = n % 2
27
- s //= 2
28
- n //= 2
29
- if a != b :
30
- incorrect .append (f'z{ i :02} ' )
31
- i += 1
32
- return incorrect
33
-
34
- def first_incorrect_z (s : int , n : int ) -> str :
35
- i = 0
36
- while s or n :
37
- a = s % 2
38
- b = n % 2
39
- s //= 2
40
- n //= 2
41
- if a != b :
42
- return f'z{ i :02} '
43
- i += 1
44
-
45
- def random_number () -> int :
46
- return random .randrange (2 ** 44 , 2 ** 45 )
47
- return random .randrange (2 ** 45 )
48
-
49
- def find_all_deps (dependencies , wire , current_set ):
50
- if dependencies [wire ][0 ][0 ] not in 'xy' :
51
- current_set .add (dependencies [wire ][0 ])
52
- find_all_deps (dependencies , dependencies [wire ][0 ], current_set )
53
- if dependencies [wire ][1 ][0 ] not in 'xy' :
54
- current_set .add (dependencies [wire ][1 ])
55
- find_all_deps (dependencies , dependencies [wire ][1 ], current_set )
56
-
57
- def to_values (num : int , letter : str , values : dict ):
58
- for i in range (45 ):
59
- a = num % 2
60
- num //= 2
61
- values [f'{ letter } { a :02} ' ] = bool (a )
62
-
63
-
64
- def compute (x : int , y : int , swap ) -> int :
65
- values = {}
66
- to_values (x , 'x' , values )
67
- to_values (y , 'y' , values )
68
- changed = True
69
- while changed :
70
- changed = False
71
- for a , oper , b , c in rul :
72
- # print(a, oper, b, c)
73
- a = swap [a ]
74
- b = swap [b ]
75
- c = swap [c ]
76
- if c not in values and a in values and b in values :
77
- if oper == 'OR' :
78
- values [c ] = values [a ] or values [b ]
79
- elif oper == 'AND' :
80
- values [c ] = values [a ] and values [b ]
81
- elif oper == 'XOR' :
82
- values [c ] = values [a ] ^ values [b ]
83
- else :
84
- print ('BAD' )
85
- changed = True
86
- # print(rul)
87
- # print(values)
88
- ooo = sorted (((k , c ) for k , c in values .items () if k [0 ] == 'z' ), key = lambda x : x [0 ], reverse = True )
89
- # print(ooo)
90
- return int ('' .join (str (int (c )) for _ , c in ooo ), base = 2 )
91
-
92
- def first_bad (swap ):
93
- x = random_number ()
94
- y = random_number ()
95
- n = x + y
96
- computed = compute (x , y , swap )
97
- if n == computed :
98
- return None
99
- return first_incorrect_z (n , computed )
100
5
101
6
def main (infi : str ):
102
7
inp = filerstrip (infi )
@@ -105,70 +10,48 @@ def main(infi: str):
105
10
for i in inputs .split ('\n ' ):
106
11
name , val = i .split (': ' )
107
12
values [name ] = bool (int (val ))
108
- # print(values)
109
- global rul
110
13
rul = []
111
- # print(rules)
112
- dependencies = {}
113
- possible_outputs = set ()
114
14
for r in rules .split ('\n ' ):
115
- # print(r)
116
15
match = re .fullmatch (r'^(.*?) (.*?) (.*?) -> (.*?)$' , r )
117
16
rul .append ((match [1 ], match [2 ], match [3 ], match [4 ]))
118
- dependencies [match [4 ]] = (match [1 ], match [3 ])
119
- possible_outputs .add (match [4 ])
120
- possible_outputs .add (match [1 ])
121
- possible_outputs .add (match [3 ])
122
- basic_swapper = {k : k for k in possible_outputs }
123
- # print([(k, c) for k, c in values.items() if k[0] == 'z'])
124
- # print(sorted(((k, c) for k, c in values.items() if k[0] == 'z'), key=lambda x: x[0], reverse=True))
125
- # ooo = sorted(((k, c) for k, c in values.items() if k[0] == 'z'), key=lambda x: x[0], reverse=True)
126
- # aaa = sorted(((k, c) for k, c in values.items() if k[0] == 'x'), key=lambda x: x[0], reverse=True)
127
- # bbb = sorted(((k, c) for k, c in values.items() if k[0] == 'y'), key=lambda x: x[0], reverse=True)
128
- # number = int(''.join(str(int(c)) for _, c in ooo), base=2)
129
- # x = int(''.join(str(int(c)) for _, c in aaa), base=2)
130
- # y = int(''.join(str(int(c)) for _, c in bbb), base=2)
131
- # print(bin(x+y))
132
- # print(bin(number))
133
-
134
- # deps = incorrect_z(x+y, number)
135
- # print(deps)
136
- # sets = []
137
- # for dep in deps:
138
- # dep_set = set()
139
- # find_all_deps(dependencies, dep, dep_set)
140
- # # print(dep_set)
141
- # sets.append(dep_set)
142
- # print(reduce(operator.and_, sets))
143
- # return
144
- # [find_all_deps(dependencies, dep, dep_set) for dep in deps]
145
- # comb = list(combinations(dep_set, 2))
146
- # # for c in combinations(comb, 4):
147
- # # if len(set(flatten(c))) != 8:
148
- # # continue
149
- # # print(c)
150
- # print(dep_set)
151
-
152
- active_swapper = basic_swapper .copy ()
153
- while True :
154
- first_incorrect = first_bad (active_swapper )
155
- print ('at' , first_incorrect )
156
- if first_incorrect is None :
157
- print (active_swapper )
158
- break
159
- dep_set = set ()
160
- find_all_deps (dependencies , first_incorrect , dep_set )
161
- for a , b in combinations (dep_set , 2 ):
162
- new_swapper = active_swapper .copy ()
163
- new_swapper [a ] = b
164
- new_swapper [b ] = a
165
- if any (int (first_bad (new_swapper )[1 :]) <= int (first_incorrect [1 :]) for i in range (100 )):
166
- continue
167
- else :
168
- active_swapper = new_swapper
169
- break
17
+ input_ands = {
18
+ d
19
+ for x , oper , y , d in rul
20
+ if oper == 'AND' and x [0 ] in 'xy' and y [0 ] in 'xy'
21
+ }
22
+ other_ands = {d for x , oper , y , d in rul if oper == 'AND' } - input_ands
23
+ carry_inputs = set (
24
+ flatten ([(x , y ) for x , oper , y , d in rul if oper == 'OR' ])
25
+ )
26
+ ands = {d for x , oper , y , d in rul if oper == 'AND' }
27
+ # Here is my notation of an adder:
28
+ # x XOR y = a c XOR a = s
29
+ # x AND y = d a AND c = b d OR b = c_out
30
+ # By manually inspecting XORs of input values (x.. and y.., grep 'XOR .* ->
31
+ # z' 24.txt | sort -k 5), we can see that z05, z09 and z30 are missing. By
32
+ # inspecting other XOR operations (those that don't output z.., grep 'XOR .*
33
+ # -> [^z]' 24.txt | sort), we can see that nbf, hdt and gbf are outputs
34
+ # XORed fom inputs other than x.. and y...
35
+ #
36
+ # The last two mismatched outputs needed are computed here. We take output
37
+ # from AND operations and compare them with inputs of ORs. Those that don't
38
+ # match are misplaced. jfb is a special case since that one is used to
39
+ # compute carry for the LSB and isn't merged with previous carry, so it's
40
+ # correct.
41
+ print (
42
+ ',' .join (
43
+ sorted (
44
+ list (
45
+ (
46
+ ands ^ carry_inputs
47
+ )
48
+ - {'jfb' }
49
+ )
50
+ + ['z05' , 'hdt' ]
51
+ )
52
+ )
53
+ )
170
54
171
- return
172
55
173
56
DAY = 24
174
57
FILE_TEST = f"{ DAY } _testa.txt"
0 commit comments