diff --git a/README.md b/README.md index e61234af..de8594da 100644 --- a/README.md +++ b/README.md @@ -28,5 +28,5 @@ Development occurs in language-specific directories: |[Day21.hs](hs/src/Day21.hs)|[Day21.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day21.kt)|[day21.py](py/aoc2023/day21.py)|[day21.rs](rs/src/day21.rs)| |[Day22.hs](hs/src/Day22.hs)|[Day22.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day22.kt)|[day22.py](py/aoc2023/day22.py)|[day22.rs](rs/src/day22.rs)| ||[Day23.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day23.kt)||[day23.rs](rs/src/day23.rs)| -|[Day24.hs](hs/src/Day24.hs) ½|[Day24.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day24.kt) ½|[day24.py](py/aoc2023/day24.py) ½|[day24.rs](rs/src/day24.rs) ½| +|[Day24.hs](hs/src/Day24.hs)|[Day24.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day24.kt) ½|[day24.py](py/aoc2023/day24.py) ½|[day24.rs](rs/src/day24.rs) ½| |[Day25.hs](hs/src/Day25.hs)|[Day25.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day25.kt)|[day25.py](py/aoc2023/day25.py)|[day25.rs](rs/src/day25.rs)| diff --git a/hs/src/Day24.hs b/hs/src/Day24.hs index b5c0fd0f..fe989e87 100644 --- a/hs/src/Day24.hs +++ b/hs/src/Day24.hs @@ -10,15 +10,19 @@ import Control.Monad ((>=>), filterM, guard, when) import Data.Char (isSpace) import Data.Functor (($>)) import Data.List (tails) +import Data.Maybe (catMaybes) +import Data.Ratio ((%), denominator, numerator) import Data.Text (Text) import qualified Data.Text as T (dropWhile, lines, singleton, stripPrefix) -import Data.Text.Read (Reader) import qualified Data.Text.Read as T (decimal, signed) +import Data.Text.Read (Reader) +import GHC.Exts (the) parseLine :: (Num a) => Reader ((a, a, a), (a, a, a)) parseLine text = do let decimal' = fmap (first fromIntegral) . T.signed T.decimal . T.dropWhile isSpace - skip token = maybe (Left $ "expected " ++ [token]) Right . T.stripPrefix (T.singleton token) . T.dropWhile isSpace + skip token = maybe (Left $ "expected " ++ [token]) Right . + T.stripPrefix (T.singleton token) . T.dropWhile isSpace (x, text) <- decimal' text (y, text) <- skip ',' text >>= decimal' (z, text) <- skip ',' text >>= decimal' @@ -41,7 +45,34 @@ part1 lo hi input = do y = m0 * x + b0 length <$> filterM ok [(line0, line1) | line0:lines' <- tails lines, line1 <- lines'] -part2 :: Text -> Either String Int +part2 :: (Integral a) => Text -> Either String a part2 input = do points <- mapM (readEntire parseLine) $ T.lines input - Left "unimplemented" + foldr (<>) (Left "no solution") $ do + ((x0, y0, z0), (vx0, vy0, vz0)):points <- tails points + let offset ((x, y, z), (vx, vy, vz)) = + ((x - x0, y - y0, z - z0), (vx - vx0, vy - vy0, vz - vz0)) + ((x1, y1, z1), (vx1, vy1, vz1)):points <- tails $ offset <$> points + let px1 = y1 * vz1 - z1 * vy1 + py1 = z1 * vx1 - x1 * vz1 + pz1 = x1 * vy1 - y1 * vx1 + guard $ px1 /= 0 || py1 /= 0 || pz1 /= 0 -- 0 and 1 are skew + ((x2, y2, z2), (vx2, vy2, vz2)):points <- tails points + let px2 = y2 * vz2 - z2 * vy2 + py2 = z2 * vx2 - x2 * vz2 + pz2 = x2 * vy2 - y2 * vx2 + guard $ px2 /= 0 || py2 /= 0 || pz2 /= 0 -- 0 and 2 are skew + let mx = py1 * pz2 - pz1 * py2 + my = pz1 * px2 - px1 * pz2 + mz = px1 * py2 - py1 * px2 + guard $ mx /= 0 || my /= 0 || mz /= 0 -- 1 and 2 are skew + let u1 = (y1 * vx1 - x1 * vy1) % (my * vx1 - mx * vy1) + u2 = (y2 * vx2 - x2 * vy2) % (my * vx2 - mx * vy2) + f _ _ _ 0 = Nothing + f m u p v = Just $ 1 % v * (m % 1 * u - p % 1) + t1 = the $ catMaybes [f mx u1 x1 vx1, f my u1 y1 vy1, f mz u1 z1 vz1] + t2 = the $ catMaybes [f mx u2 x2 vx2, f my u2 y2 vy2, f mz u2 z2 vz2] + let offset = (mx + my + mz) % 1 * (u1 * t2 - u2 * t1) / (t2 - t1) + pure $ if denominator offset /= 1 + then Left "non-integral solution" + else Right $ x0 + y0 + z0 + numerator offset diff --git a/hs/test/Day24Spec.hs b/hs/test/Day24Spec.hs index 1314cc3a..d11f3102 100644 --- a/hs/test/Day24Spec.hs +++ b/hs/test/Day24Spec.hs @@ -4,7 +4,7 @@ module Day24Spec (spec) where import Data.Text (Text) import qualified Data.Text as T (unlines) import Day24 (part1, part2) -import Test.Hspec (Spec, describe, it, shouldBe, xit) +import Test.Hspec (Spec, describe, it, shouldBe) example :: Text example = T.unlines @@ -21,5 +21,5 @@ spec = do it "examples" $ do part1 7 27 example `shouldBe` Right 2 describe "part 2" $ do - xit "examples" $ do + it "examples" $ do part2 example `shouldBe` Right 47