|
| 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