Skip to content

Commit 2d8a20b

Browse files
committed
Day 14: Look for combination of max per-axis safety
1 parent 606f727 commit 2d8a20b

File tree

4 files changed

+98
-56
lines changed

4 files changed

+98
-56
lines changed

hs/aoc2024.cabal

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ source-repository head
2121
library
2222
hs-source-dirs: src
2323
exposed-modules:
24+
Common
2425
Day1
2526
Day10
2627
Day11
@@ -47,9 +48,6 @@ library
4748
Day8
4849
Day9
4950

50-
other-modules:
51-
Common
52-
5351
build-depends:
5452
array ^>=0.5.7.0,
5553
async ^>=2.2.5,
@@ -90,6 +88,7 @@ test-suite aoc2024-test
9088
hs-source-dirs: test
9189
main-is: Main.hs
9290
other-modules:
91+
CommonSpec
9392
Day10Spec
9493
Day11Spec
9594
Day12Spec
@@ -117,6 +116,7 @@ test-suite aoc2024-test
117116
Day9Spec
118117

119118
build-depends:
119+
QuickCheck ^>=2.15.0.1,
120120
aoc2024,
121121
base >=4.20 && <4.22,
122122
hspec ^>=2.11.10,

hs/src/Common.hs

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,38 @@
1-
module Common (groupConsecutiveBy, readEntire, readMany, readSome) where
1+
module Common (crt, egcd, readEntire, readMany, readSome) where
22

3-
import Control.Arrow (first)
3+
import Data.Bifunctor (first)
44
import Data.Char (isSpace)
55
import Data.List.NonEmpty (NonEmpty ((:|)))
66
import Data.Text (Text)
77
import Data.Text qualified as T (dropWhile, null)
88
import Data.Text.Read (Reader)
99

10-
groupConsecutiveBy :: (a -> a -> Bool) -> [a] -> [[a]]
11-
groupConsecutiveBy f xs = chunk id $ zip xs $ True : zipWith f xs (drop 1 xs)
10+
-- | Chinese remainder theorem.
11+
--
12+
-- prop> crt (r1, q1) (r2, q2) == (r3, q3) ==>
13+
-- r3 `mod` q1 == r1 && q3 `mod` q1 == 0 &&
14+
-- r3 `mod` q2 == r2 && q3 `mod` q2 == 0
15+
crt :: (Integral a) => (a, a) -> (a, a) -> (a, a)
16+
crt (r1, q1) (r2, q2) = (r3 `mod` q3, q3)
1217
where
13-
chunk k [] = filter (not . null) [k []]
14-
chunk k ((x, False) : rest) = k [] : chunk (x :) rest
15-
chunk k ((x, True) : rest) = chunk (k . (x :)) rest
18+
q3 = lcm q1 q2
19+
-- r3 * q2 == r1 * q2 (mod q3)
20+
-- r3 * q3 == r2 * q1 (mod q3)
21+
-- r3 * (q1 + q2) = r1 * q2 + r2 * q1 (mod q3)
22+
(t, _, g) = egcd (q1 + q2) q3
23+
-- t * (q1 + q2) == g (mod q3)
24+
-- r3 = (r1 * q2 + r2 * q1) * t / g (mod q3)
25+
(r3, 0) = ((r1 * q2 + r2 * q1) * t) `divMod` g
26+
27+
-- | Extended GCD.
28+
--
29+
-- prop> gcd a b == (s, t, g) ==> a * s + b * t == g
30+
egcd :: (Integral a) => a -> a -> (a, a, a)
31+
egcd a 0 = (1, 0, a)
32+
egcd a b = (t, s - q * t, g)
33+
where
34+
(q, r) = a `quotRem` b
35+
(s, t, g) = egcd b r
1636

1737
readEntire :: Reader a -> Text -> Either String a
1838
readEntire reader input = do

hs/src/Day14.hs

Lines changed: 32 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -5,72 +5,58 @@
55
-- Description: <https://adventofcode.com/2024/day/14 Day 14: Restroom Redoubt>
66
module Day14 (part1, part1', part2) where
77

8-
import Common (groupConsecutiveBy)
9-
import Control.Monad (join, liftM2)
10-
import Control.Parallel.Strategies (parList, rdeepseq, withStrategy)
11-
import Data.Char (intToDigit)
12-
import Data.Map qualified as Map (findWithDefault)
13-
import Data.Map.Strict qualified as Map (fromListWith)
14-
import Data.Ord (Down (Down))
15-
import Data.Set qualified as Set (fromList, toList)
8+
import Common (crt)
9+
import Control.Monad (ap)
10+
import Data.Foldable (foldMap')
11+
import Data.Semigroup (Arg (Arg), Sum (Sum))
1612
import Data.String (IsString)
1713
import Data.Text (Text)
1814
import Data.Void (Void)
19-
import Debug.Trace (traceM)
2015
import Text.Megaparsec (MonadParsec, ParseErrorBundle, Stream (Token, Tokens), parse, sepEndBy1)
2116
import Text.Megaparsec.Char (char, newline, string)
2217
import Text.Megaparsec.Char.Lexer qualified as L (decimal, signed)
2318

2419
parser :: (MonadParsec e s m, IsString (Tokens s), Token s ~ Char, Num a) => m [((a, a), (a, a))]
2520
parser = line `sepEndBy1` newline
2621
where
27-
line = (,) <$> (string "p=" *> v2) <*> (string " v=" *> v2)
28-
v2 = (,) <$> (L.signed (pure ()) L.decimal <* char ',') <*> L.signed (pure ()) L.decimal
22+
line = do
23+
x <- string "p=" *> L.signed (pure ()) L.decimal
24+
y <- char ',' *> L.signed (pure ()) L.decimal
25+
vx <- string " v=" *> L.signed (pure ()) L.decimal
26+
vy <- char ',' *> L.signed (pure ()) L.decimal
27+
pure ((x, vx), (y, vy))
2928

3029
part1 :: Text -> Either (ParseErrorBundle Text Void) Int
3130
part1 = part1' 101 103
3231

3332
part1' :: Int -> Int -> Text -> Either (ParseErrorBundle Text Void) Int
3433
part1' width height input = do
3534
robots <- parse parser "" input
36-
let totals =
37-
Map.fromListWith (+) $
38-
[ ((compare x $ width `div` 2, compare y $ height `div` 2), 1)
39-
| ((x0, y0), (vx, vy)) <- robots,
40-
let x = (x0 + vx * t) `mod` width
41-
y = (y0 + vy * t) `mod` height
42-
]
43-
pure $ product [Map.findWithDefault 0 k totals | k <- join (liftM2 (,)) [LT, GT]]
35+
let (Sum q1, Sum q2, Sum q3, Sum q4) = foldMap' f robots
36+
pure $ q1 * q2 * q3 * q4
4437
where
4538
t = 100
39+
f ((x, vx), (y, vy)) = case ( compare ((x + t * vx) `mod` width) (width `div` 2),
40+
compare ((y + t * vy) `mod` height) (height `div` 2)
41+
) of
42+
(LT, LT) -> (Sum 1, Sum 0, Sum 0, Sum 0)
43+
(LT, GT) -> (Sum 0, Sum 1, Sum 0, Sum 0)
44+
(GT, LT) -> (Sum 0, Sum 0, Sum 1, Sum 0)
45+
(GT, GT) -> (Sum 0, Sum 0, Sum 0, Sum 1)
46+
_ -> mempty
4647

4748
part2 :: Text -> Either (ParseErrorBundle Text Void) Int
4849
part2 input = do
49-
robots <- parse parser "" input
50-
let (_, bestTime) =
51-
minimum . withStrategy (parList rdeepseq) $
52-
[ (Down $ maximum $ map length verticalLines, t)
53-
| t <- [0 .. lcm width height - 1],
54-
let verticalLines =
55-
groupConsecutiveBy isLine . Set.toList . Set.fromList $
56-
[ ((y0 + vy * t) `mod` height, (x0 + vx * t) `mod` width)
57-
| ((x0, y0), (vx, vy)) <- robots
58-
]
59-
isLine (y0, x0) (y1, x1) = y0 == y1 && x0 + 1 == x1
60-
]
61-
positions =
62-
Map.fromListWith (+) $
63-
[ (((x0 + vx * bestTime) `mod` width, (y0 + vy * bestTime) `mod` height), 1)
64-
| ((x0, y0), (vx, vy)) <- robots
65-
]
66-
line y =
67-
[ case Map.findWithDefault 0 (x, y) positions of
68-
0 -> '.'
69-
n -> if n < 10 then intToDigit n else '+'
70-
| x <- [0 .. width - 1]
71-
]
72-
mapM_ (traceM . line) [0 .. height - 1]
73-
pure bestTime
50+
(xrobots, yrobots) <- unzip <$> parse parser "" input
51+
let Arg _ x = maximum $ (flip Arg `ap` score xrobots width) <$> [0 .. width - 1]
52+
Arg _ y = maximum $ (flip Arg `ap` score yrobots height) <$> [0 .. height - 1]
53+
pure $ fst $ crt (x, width) (y, height)
7454
where
75-
width = 101
76-
height = 103
55+
(width, height) = (101, 103)
56+
score robots m t = max h1 h2 :: Int
57+
where
58+
(Sum h1, Sum h2) = foldMap' f robots
59+
f (p, v) = case compare ((p + t * v) `mod` m) (m `div` 2) of
60+
LT -> (Sum 1, Sum 0)
61+
EQ -> (Sum 0, Sum 0)
62+
GT -> (Sum 0, Sum 1)

hs/test/CommonSpec.hs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
module CommonSpec (spec) where
2+
3+
import Common (crt, egcd)
4+
import Test.Hspec (Spec, describe)
5+
import Test.Hspec.QuickCheck (prop)
6+
import Test.QuickCheck (Large (Large), arbitrarySizedIntegral, conjoin, counterexample, cover, property, suchThat, (===))
7+
8+
spec :: Spec
9+
spec = do
10+
describe "crt" $ do
11+
prop "is correct" $ do
12+
q1 <- arbitrarySizedIntegral `suchThat` (/= 0)
13+
q2 <- arbitrarySizedIntegral `suchThat` (/= 0)
14+
let q = gcd q1 q2
15+
r1 <- arbitrarySizedIntegral `suchThat` ((== 0) . (`mod` q))
16+
r2 <- arbitrarySizedIntegral `suchThat` ((== 0) . (`mod` q))
17+
let (r3, q3) = crt @Int (r1, q1) (r2, q2)
18+
pure
19+
. cover 90 (abs q1 /= 1 && abs q1 /= 1) "non-trivial"
20+
. counterexample ("(r1,q1) = " ++ show (r1, q1))
21+
. counterexample ("(r2,q2) = " ++ show (r2, q2))
22+
. counterexample ("(r3,q3) = " ++ show (r3, q3))
23+
$ conjoin
24+
[ counterexample "r1 == r3 (mod q1)" $ r1 `mod` q1 === r3 `mod` q1,
25+
counterexample "q3 == 0 (mod q1)" $ q3 `mod` q1 === 0,
26+
counterexample "r2 == r3 (mod q2)" $ r2 `mod` q2 === r3 `mod` q2,
27+
counterexample "q3 == 0 (mod q2)" $ q3 `mod` q2 === 0,
28+
counterexample "|r3| < |q3|" $ property $ abs r3 < abs q3,
29+
counterexample "|gcd(r1 - r2, q1, q2)| == |q1 * q2 / q3|" $
30+
abs (gcd (r1 - r2) $ gcd q1 q2) === abs (q1 * q2 `div` q3)
31+
]
32+
describe "egcd" $ do
33+
prop "is correct" $ \(Large a) (Large b) ->
34+
cover 90 (abs a > 1 && abs b > 1) "non-trivial" $
35+
let (s, t, g) = egcd @Int a b
36+
in a * s + b * t === g

0 commit comments

Comments
 (0)