Skip to content

Commit c647019

Browse files
committed
2023 d24: Add clean solution
1 parent 60d8c5c commit c647019

2 files changed

Lines changed: 150 additions & 1 deletion

File tree

2023/original_solutions/day24.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def det(a, b):
6868
import z3
6969

7070
# BitVec is way faster than Int
71-
I = lambda name: z3.BitVec(name, 64)
71+
I = lambda name: z3.Real(name)
7272

7373
x, y, z = I('x'), I('y'), I('z')
7474
vx, vy, vz = I('vx'), I('vy'), I('vz')
@@ -90,5 +90,7 @@ def det(a, b):
9090
x, y, z = m.eval(x), m.eval(y), m.eval(z)
9191
x, y, z = x.as_long(), y.as_long(), z.as_long()
9292

93+
print(x, y, z)
94+
9395
ans2 = x + y + z
9496
advent.print_answer(2, ans2)

2023/solutions/day24.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
#!/usr/bin/env python3
2+
3+
import sys
4+
from itertools import combinations
5+
6+
def matrix_transpose(m):
7+
return list(map(list, zip(*m)))
8+
9+
def matrix_minor(m, i, j):
10+
return [row[:j] + row[j + 1:] for row in (m[:i] + m[i + 1:])]
11+
12+
def matrix_det(m):
13+
if len(m) == 2:
14+
return m[0][0] * m[1][1] - m[0][1] * m[1][0]
15+
16+
determinant = 0
17+
for c in range(len(m)):
18+
determinant += ((-1)**c) * m[0][c] * matrix_det(matrix_minor(m, 0, c))
19+
20+
return determinant
21+
22+
# Adapted from: https://stackoverflow.com/a/39881366/3889449
23+
def matrix_inverse(m):
24+
determinant = matrix_det(m)
25+
cofactors = []
26+
27+
for r in range(len(m)):
28+
row = []
29+
30+
for c in range(len(m)):
31+
minor = matrix_minor(m, r, c)
32+
row.append(((-1)**(r + c)) * matrix_det(minor))
33+
34+
cofactors.append(row)
35+
36+
cofactors = matrix_transpose(cofactors)
37+
38+
for r in range(len(cofactors)):
39+
for c in range(len(cofactors)):
40+
cofactors[r][c] /= determinant
41+
42+
return cofactors
43+
44+
# Adapted from: https://stackoverflow.com/a/20677983/3889449
45+
def intersection_2d_forward(a, va, b, vb):
46+
a1 = (a[0] + va[0], a[1] + va[1])
47+
b1 = (b[0] + vb[0], b[1] + vb[1])
48+
dx = (a[0] - a1[0], b[0] - b1[0])
49+
dy = (a[1] - a1[1], b[1] - b1[1])
50+
51+
div = matrix_det((dx, dy))
52+
if div == 0:
53+
return None, None
54+
55+
d = (matrix_det((a, a1)), matrix_det((b, b1)))
56+
57+
x = matrix_det((d, dx)) / div
58+
if (x > a[0]) != (va[0] > 0) or (x > b[0]) != (vb[0] > 0):
59+
return None, None
60+
61+
y = matrix_det((d, dy)) / div
62+
if (y > a[1]) != (va[1] > 0) or (y > b[1]) != (vb[1] > 0):
63+
return None, None
64+
65+
return x, y
66+
67+
def vector_diff(a, b):
68+
return a[0] - b[0], a[1] - b[1], a[2] - b[2]
69+
70+
def get_equations(a, va, b, vb):
71+
# Return the coefficient matrix (A) and the constant terms vector (B) for
72+
# the 3 equations given by:
73+
#
74+
# (p - a) X (v - va) == (p - b) X (v - vb)
75+
76+
dx, dy, dz = vector_diff(a, b)
77+
dvx, dvy, dvz = vector_diff(va, vb)
78+
79+
A = [
80+
[0, -dvz, dvy, 0, -dz, dy],
81+
[dvz, 0, -dvx, dz, 0, -dx],
82+
[-dvy, dvx, 0, -dy, dx, 0]
83+
]
84+
85+
B = [
86+
b[1]*vb[2] - b[2]*vb[1] - (a[1]*va[2] - a[2]*va[1]),
87+
b[2]*vb[0] - b[0]*vb[2] - (a[2]*va[0] - a[0]*va[2]),
88+
b[0]*vb[1] - b[1]*vb[0] - (a[0]*va[1] - a[1]*va[0]),
89+
]
90+
91+
return A, B
92+
93+
def matrix_mul(m, vec):
94+
res = []
95+
96+
for row in m:
97+
res.append(sum(r * v for r, v in zip(row, vec)))
98+
99+
return res
100+
101+
def solve(hailstones):
102+
(a, va), (b, vb), (c, vc) = hailstones[:3]
103+
104+
# Let our 6 unknowns be: p = (x, y, z) and v = (vx, vy, vz)
105+
# Solve the linear system of 6 equations given by:
106+
#
107+
# (p - a) X (v - va) == (p - b) X (v - vb)
108+
# (p - a) X (v - va) == (p - c) X (v - vc)
109+
#
110+
# Where X represents the vector cross product.
111+
112+
A1, B1 = get_equations(a, va, b, vb)
113+
A2, B2 = get_equations(a, va, c, vc)
114+
A = A1 + A2
115+
B = B1 + B2
116+
117+
# Could also use fractions.Fraction to avoid rounding mistakes
118+
x = matrix_mul(matrix_inverse(A), B)
119+
return sum(map(round, x[:3]))
120+
121+
122+
# Open the first argument as input or use stdin if no arguments were given
123+
fin = open(sys.argv[1], 'r') if len(sys.argv) > 1 else sys.stdin
124+
125+
hailstones = []
126+
total = 0
127+
128+
with fin:
129+
for line in fin:
130+
p, v = line.split('@')
131+
p = tuple(map(int, p.split(',')))
132+
v = tuple(map(int, v.split(',')))
133+
hailstones .append((p, v))
134+
135+
for a, b in combinations(hailstones, 2):
136+
x, y = intersection_2d_forward(*a, *b)
137+
if x is None:
138+
continue
139+
140+
xok = 200000000000000 <= x <= 400000000000000
141+
yok = 200000000000000 <= y <= 400000000000000
142+
total += xok and yok
143+
144+
print('Part 1:', total)
145+
146+
answer = solve(hailstones)
147+
print('Part 2:', answer)

0 commit comments

Comments
 (0)