Skip to content

Commit ec6beeb

Browse files
committed
day 13 reflections
1 parent d2c8e57 commit ec6beeb

File tree

2 files changed

+56
-2
lines changed

2 files changed

+56
-2
lines changed

2024/AOC2024/Day13.hs

+1-2
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,8 @@ getPrize :: V2 Point -> Point -> Maybe Int
3232
getPrize coeff targ = do
3333
(det, invCoeff) <- inv22Int (distribute coeff)
3434
let resDet = invCoeff !* targ
35-
residues = (`mod` det) <$> resDet
3635
V2 a b = (`div` det) <$> resDet
37-
guard $ all (== 0) residues
36+
guard $ all ((== 0) . (`mod` det)) resDet
3837
pure $ 3 * a + b
3938

4039
day13a :: [(V2 Point, Point)] :~> Int

reflections/2024/day13.md

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
This one reduces to basically solving two linear equations, but it's kind of
2+
fun to see what the *linear* haskell library gives us to make things more
3+
convenient.
4+
5+
Basically for `xa`, `ya`, `xb`, `yb`, we want to solve the matrix equation `M p
6+
= c` for `p`, where `c` is our target `<x, y>`, and `M` is `[ xa xb; ya yb ]`. We're
7+
going to assume that our two buttons are linearly independent (they are not
8+
multiples of each other). Note that the `M` matrix is the transpose of the
9+
numbers as we originally parse them.
10+
11+
Normally we can solve this as `p = M^-1 C`, where `M^-1 = [ yb -xb; -ya xa] /
12+
(ad - bc)`. However, we only care about integer solutions. This means that we
13+
can do some checks:
14+
15+
1. Compute `det = ad - bc` and a matrix `U = [yb -xb ; -ya xa]`, which is
16+
`M^-1 * det`.
17+
2. Compute `p*det = U c`
18+
3. Check that `det` is not 0
19+
4. Check that ``(`mod` det)`` is 0 for all items in `U c`
20+
5. Our result is then the ``(`div` det)`` for all items in `U c`.
21+
22+
*linear* has the `det22` method for the determinant of a 2x2 matrix, but it
23+
doesn't quite have the `M^-1 * det` function, it only has `M^-1` for
24+
`Fractional` instances. So we can write our own:
25+
26+
```haskell
27+
-- | Returns det(A) and inv(A)det(A)
28+
inv22Int :: (Num a, Eq a) => M22 a -> Maybe (a, M22 a)
29+
inv22Int m@(V2 (V2 a b) (V2 c d))
30+
| det == 0 = Nothing
31+
| otherwise = Just (det, V2 (V2 d (-b)) (V2 (-c) a))
32+
where
33+
det = det22 m
34+
35+
type Point = V2 Int
36+
37+
getPrize :: V2 Point -> Point -> Maybe Int
38+
getPrize coeff targ = do
39+
(det, invTimesDet) <- inv22Int (transpose coeff)
40+
let resTimesDet = invTimesDet !* targ
41+
V2 a b = (`div` det) <$> resTimesDet
42+
guard $ all ((== 0) . (`mod` det)) resTimesDet
43+
pure $ 3 * a + b
44+
45+
part1 :: [(V2 Point, Point)] -> Int
46+
part1 = sum . mapMaybe (uncurry getPrize)
47+
48+
part2 :: [(V2 Point, Point)] -> Int
49+
part2 = part2 . map (second (10000000000000 +))
50+
```
51+
52+
Here we take advantage of `transpose`, `det22`, `!*` for matrix-vector
53+
multiplication, the `Functor` instance of vectors for `<$>`, the `Foldable`
54+
instance of vectors for `all`, and the `Num` instance of vectors for numeric
55+
literals and `+`.

0 commit comments

Comments
 (0)