-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathday19.py
executable file
·224 lines (175 loc) · 4.32 KB
/
day19.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
#!/usr/bin/env python3
from utils.all import *
DEBUG = 'debug' in map(str.lower, sys.argv)
if not DEBUG:
fin = advent.get_input()
def cos(x):
if x == 90:
return 0
if x == 180:
return -1
if x == 270:
return 0
assert x == 0
return 1
def sin(x):
if x == 90:
return 1
if x == 180:
return 0
if x == 270:
return -1
assert x == 0
return 0
def rotate3d_z(x, y, z, theta):
c, s = cos(theta), sin(theta)
return (x*c - y*s, x*s+y*c, z)
def rotate3d_x(x, y, z, theta):
c, s = cos(theta), sin(theta)
return (x, y*c - z*s, y*s + z*c)
def rotate3d_y(x, y, z, theta):
c, s = cos(theta), sin(theta)
return (x*c + z*s, y, -x*s + z*c)
def apply(rx, ry, rz):
def f(x, y, z):
nonlocal rx, ry, rz
x, y, z = rotate3d_x(x, y, z, rx)
x, y, z = rotate3d_y(x, y, z, ry)
return rotate3d_z(x, y, z, rz)
return f
facings = [
apply( 0, 0, 0),
apply( 90, 0, 0),
apply(180, 0, 0),
apply(270, 0, 0),
apply( 0, 90, 0),
apply( 90, 90, 0),
apply(180, 90, 0),
apply(270, 90, 0),
apply( 0, 180, 0),
apply( 90, 180, 0),
apply(180, 180, 0),
apply(270, 180, 0),
apply( 0, 270, 0),
apply( 90, 270, 0),
apply(180, 270, 0),
apply(270, 270, 0),
apply( 0, 0, 90),
apply( 90, 0, 90),
apply(180, 0, 90),
apply(270, 0, 90),
apply( 0, 0, 270),
apply( 90, 0, 270),
apply(180, 0, 270),
apply(270, 0, 270),
]
assert len(facings) == 24
p = (1, 2, 3)
s = set()
for f in facings:
s.add(f(*p))
assert len(s) == 24
if DEBUG:
fin = io.StringIO('''\
--- scanner 0 ---
0,2,0
4,1,0
3,3,0
--- scanner 1 ---
-1,-1,0
-5,0,0
-2,1,0
0,1,0
1,1,0
2,1,0
--- scanner 2 --- basis with inverted x and y axis
0,2,1
1,2,1
2,2,1
''')
lines = get_lines(fin)
timer_start()
ans = 0
scanners = []
for line in lines:
if not line:
continue
if line.startswith('---'):
scanners.append([])
continue
scanners[-1].append(tuple(map(int, line.split(','))))
scanners = list(map(set, scanners))
vbase = ((1, 0, 0), (0, 1, 0), (0, 0, 1))
vx, vy, vz = vbase
bases = []
for f in facings:
bases.append((f(*vx), f(*vy), f(*vz)))
# dump_iterable(sorted(bases))
assert len(set(bases)) == 24
# transform to coords from given basis to basis ((1, 0, 0), (0, 1, 0), (0, 0, 1))
def basis_transform(v, basis):
a,b,c = basis[0]
d,e,f = basis[1]
g,h,i = basis[2]
x,y,z = v
return (a*x+b*y+c*z, d*x+e*y+f*z, g*x+h*y+i*z)
def diff(a, b):
ax, ay, az = a
bx, by, bz = b
return (ax - bx, ay - by, az - bz)
def add(a, b):
ax, ay, az = a
bx, by, bz = b
return (ax + bx, ay + by, az + bz)
def manhattan(a, b):
ax, ay, az = a
bx, by, bz = b
return abs(ax - bx) + abs(ay - by) + abs(az - bz)
print('there are', len(scanners), 'scanners')
MATCH_THRESHOLD = 12
def common_points(s1, s2, i1, i2):
# assume s1 is in "normal" base ((1, 0, 0), (0, 1, 0), (0, 0, 1))
# make s2 face any possible way (any base), then convert its points to normal base
#
# then for each pair of points p1, p2 (p2 in normal base):
# if p1 and p2 identify the same beacon for s1 and s2, then p1 - p2 is the
# distance vector from s1 to s2
# this means that if we get this distance, and translate all points of s2 by
# this distance, we can check if at least 12 should line up with those of s1
for basis in bases:
new_s2 = tuple(basis_transform(vec, basis) for vec in s2)
for p1, p2 in product(s1, new_s2):
dist = diff(p1, p2)
translated_s2 = set(add(p, dist) for p in new_s2)
if len(s1 & translated_s2) >= MATCH_THRESHOLD:
eprint('scanners', i1, 'and', i2, 'match: distance', dist, 'basis', basis)
return translated_s2, dist
return None, None
matched = {0: scanners[0]}
distances = [(0,0,0)]
done = set()
while True:
for i in range(len(scanners)):
if i in done or i not in matched:
continue
for j in range(len(scanners)):
if j in matched or i == j:
continue
common, dist = common_points(matched[i], scanners[j], i, j)
if common is not None:
matched[j] = common
distances.append(dist)
done.add(i)
if len(done) == len(scanners):
break
all_points = reduce(set.union, matched.values())
ans = len(all_points)
# 799 bad
advent.print_answer(1, ans)
# dump_dict(distances)
best = max(starmap(manhattan, combinations(distances, 2)))
# 12238 nope
advent.print_answer(2, best)
# Bif OOF!
# cpython Timer ./day19.py: 20.218s wall, 20.217s CPU
# pypy Timer ./day19.py: 7.776s wall, 7.776s CPU