6 votes

Day 24: Never Tell Me The Odds

Today's problem description: https://adventofcode.com/2023/day/24

Please post your solutions in your own top-level comment. Here's a template you can copy-paste into your comment to format it nicely, with the code collapsed by default inside an expandable section with syntax highlighting (you can replace python with any of the "short names" listed in this page of supported languages):

<details>
<summary>Part 1</summary>

```python
Your code here.
```

</details>

3 comments

  1. RheingoldRiver
    Link
    Math is hard so I used ChatGPT to help me with numpy for part 1 (which honestly took so many attempts that it was probably more work than me doing it myself). I figured out the equations for part...

    Math is hard so I used ChatGPT to help me with numpy for part 1 (which honestly took so many attempts that it was probably more work than me doing it myself). I figured out the equations for part 2 pretty quickly after seeing the problem but I didn't want to relive high school math & ChatGPT wouldn't give me numpy that worked. So my friend put my equations into Python Z3 lib and then neither of us had to do any math, hooray!

    Part 2 explanation What we have is basically 9 unknowns, and a lot of equations. But we only need to use 3 sets of 3 equations that are linearly independent to solve this.

    The unknowns we need are px, py, pz, vx, vy, vz, t, s, and r; where t, s, and r are the 3 times of intersection.

    So we can determine these by:

    • px1 + vx1 * t = px + vx * t
    • py1 + vy1 * t = py + vy * t
    • pz1 + vz1 * t = pz + vz * t
    • px2 + vx2 * s = px + vx * s
    • py2 + vy2 * s = py + vy * s
    • pz2 + vz2 * s = pz + vz * s
    • px3 + vx3 * r = px + vx * r
    • py3 + vy3 * r = py + vy * r
    • pz3 + vz3 * r = pz + vz * r

    Where px1, py1, pz1, vx1, vy1, vz1, etc, are the positions & velocities of the first three points on our list.

    (Except we get trolled in the test input and the system is under-determined in the first 3 so we have to look a bit later. TBH I didn't consider this when attempting to solve with numpy, and this might be why I got the wrong answer for the test input, maybe it would've worked if I tried 3 different points. But I don't want to go back and try again.)

    Python code

    1 vote
  2. scarecrw
    Link
    Part 1 went quite smoothly, and I finished at my highest place this year at 122! Not quite the leaderboard, but much better than I was ever expecting to place this year. Nothing tricky, just...

    Part 1 went quite smoothly, and I finished at my highest place this year at 122! Not quite the leaderboard, but much better than I was ever expecting to place this year. Nothing tricky, just parsing the input, finding the intersections, and checking the bounds.

    Part 2 was not so nice, but I got there eventually. My solution relies on recognizing that if you rotate the vectors at a certain angle, they should all align to intersect at a singular point. I first got stuck trying to figure out how to rotate 3d vectors, but then realized that I don't actually need to rotate the vectors, I can just shift their x and y velocities until they align. My solution simply tries a range of values for the x and y velocities until all hailstones intersect at a singular point, that point being the x and y coordinates for the answer. I then repeat the process for the x and z velocities/coordinates to get the final position.

    Haskell Solution
    module Main (main) where
    import AOCTools (splitOn, splitBy)
    import Data.Ratio ((%), numerator)
    import Data.Maybe (fromJust, catMaybes, mapMaybe)
    
    lowerBound = 200000000000000
    upperBound = 400000000000000
    
    main :: IO ()
    main = do
        input <- readFile "./input.txt"
        putStrLn $ "Part 1: " ++ show (solve1 $ parseInput input)
        putStrLn $ "Part 2: " ++ show (solve2 $ parseInput input)
    
    data Coord = Coord {x :: Rational, y :: Rational, z :: Rational} deriving (Show)
    type Position = Coord
    type Velocity = Position
    data Hailstone = Hailstone {position :: Position, velocity :: Velocity} deriving (Show)
    
    parseInput :: String -> [Hailstone]
    parseInput input = map parseHailstone (lines input)
    
    parseHailstone :: String -> Hailstone
    parseHailstone str = Hailstone (Coord x y z) (Coord dx dy dz) where
        (pos, vel) = splitOn " @ " str
        [x, y, z] = map (toRational . read) $ splitBy ", " pos
        [dx, dy, dz] = map (toRational . read) $ splitBy ", " vel
    
    solve1 :: [Hailstone] -> Int
    solve1 hailstones = (`div` 2) . length . filter withinBounds .catMaybes $ intersections where
        intersections = [findXYIntersect h1 h2 | h1 <- hailstones, h2 <- hailstones]
        withinBounds (x, y) = lowerBound <= x && x <= upperBound && lowerBound <= y && y <= upperBound
    
    findXYIntersect :: Hailstone -> Hailstone -> Maybe (Rational, Rational)
    findXYIntersect (Hailstone (Coord x1  y1 _) (Coord dx1 dy1 _)) (Hailstone (Coord x2 y2 _) (Coord dx2 dy2 _)) = res where
        res = if dx1 == 0 || dx2 == 0 || m1 == m2 || not inFuture then Nothing else Just (x, y)
        m1 = dy1 / dx1
        m2 = dy2 / dx2
        x = ((y2 - y1) + m1 * x1 - m2 * x2) / (m1 - m2)
        y = m1 * (x - x1) + y1
        inFuture = x `compare` x1 == dx1 `compare` 0 && x `compare` x2 == dx2 `compare` 0
    
    solve2 :: [Hailstone] -> Integer
    solve2 hailstones = numerator (x + y + z) where
        possibleVelocities = [(dx, dy) | dx <- [-300..300], dy <- [-300..300]]
        (x, y) = head $ mapMaybe (uncurry $ converge hailstones) possibleVelocities
        flippedHailstones = map swapYZ hailstones
        (_, z) = head $ mapMaybe (uncurry $ converge flippedHailstones) possibleVelocities
    
    converge :: [Hailstone] -> Rational -> Rational -> Maybe (Rational, Rational)
    converge hailstones dx dy = res where
        (s1:s2:hailstones') = map (shiftHailstone dx dy) hailstones
        res = case findXYIntersect s1 s2 of
            Nothing -> Nothing
            Just (x, y)
                | all (\r -> findXYIntersect s1 r == Just (x, y)) (s2:hailstones') -> Just (x, y)
                | otherwise -> Nothing
    
    shiftHailstone :: Rational -> Rational -> Hailstone -> Hailstone
    shiftHailstone refdx refdy (Hailstone (Coord x y z) (Coord dx dy dz)) =
        Hailstone (Coord x y z) (Coord (dx-refdx) (dy-refdy) dz)
    
    swapYZ :: Hailstone -> Hailstone
    swapYZ (Hailstone (Coord x y z) (Coord dx dy dz)) = Hailstone (Coord x z y) (Coord dx dz dy)
    
    1 vote
  3. fidwell
    Link
    This one was miserable. My solution sounds pretty similar to scarecrw's on the sufrace. The awfulness mostly came from translating linear algebra equations into code. I wouldn't mind doing it on...

    This one was miserable. My solution sounds pretty similar to scarecrw's on the sufrace. The awfulness mostly came from translating linear algebra equations into code. I wouldn't mind doing it on paper, but typing it all out was so error-prone, and I spent hours tracking down mysterious bugs. (I think ChatGPT created most of those bugs, so thanks for nothing, you've been really useless.) I got there eventually, but mostly at the mercy of people who have already done the hard work on StackOverflow. After figuring that out, it was just a matter of brute-forcing guesses until we get a solution. I'm still not even convinced my algorithm is truly correct, since my unit tests felt a little dodgy. But I got the star so I'm ready to forget about it altogether.

    C# code - solution runner, ray collision

    1 vote