-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDay09Bridge.hs
76 lines (64 loc) · 2.46 KB
/
Day09Bridge.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
import Control.Monad (liftM)
import qualified Data.IntMap.Strict as I
import Data.List (foldl')
import Data.List.Split (endBy)
import qualified Data.Set as S
import Formatting
type Point = (Int, Int)
data Rope = Rope
{ knots :: I.IntMap Point,
tailHistory :: S.Set Point
}
startingRope :: Int -> Rope
startingRope len =
Rope
{ knots = I.fromList $ zip [1 .. len] (repeat (0, 0)),
tailHistory = S.empty
}
main :: IO ()
main = do
input <- liftM (endBy "\n") . readFile $ "input/day09.txt"
let inputMoves = concat . map parseMove $ input
length2Total = countTailPositions 2 inputMoves -- Expected: 6332
length10Total = countTailPositions 10 inputMoves -- Expected: 2511
fprintLn ("Part 1 result (2 knots): " % int) length2Total
fprintLn ("Part 2 result (10 knots): " % int) length10Total
where
countTailPositions len = S.size . tailHistory . foldl' moveRope (startingRope len)
-- Point transformation (i.e. tuple addition)
infixl 6 <+>
(<+>) :: Point -> (Int, Int) -> Point
(x, y) <+> (dx, dy) = (x + dx, y + dy)
-- Moves are broken up into individual steps since the tail has to be updated after every one
parseMove :: String -> [(Int, Int)]
parseMove (direction : _ : amount) = replicate (read amount) increment
where
increment = case direction of
'U' -> (0, 1)
'D' -> (0, -1)
'L' -> (-1, 0)
'R' -> (1, 0)
-- Updates the positions of every knot along the rope after performing the given move
moveRope :: Rope -> (Int, Int) -> Rope
moveRope Rope {knots, tailHistory} move = Rope {knots = newKnots, tailHistory = S.insert newTail tailHistory}
where
newKnots = snd $ I.mapAccumWithKey updateKnot move knots
-- findMax returns the value associated with the maximum key in a map
-- in this case, it always returns the position of the tail
(_, newTail) = I.findMax newKnots
updateKnot :: Point -> Int -> Point -> (Point, Point)
updateKnot prevNew knotNum current
-- for the first knot, prevNew is just the input move
| knotNum == 1 = dupe $ current <+> prevNew
-- already close enough to the previous knot
| abs dx < 2 && abs dy < 2 = dupe current
-- move 1 step in one (or both) directions towards previous knot
| otherwise = dupe $ current <+> (signum dx, signum dy)
where
(dx, dy) = prevNew <+> negateTuple current
negateTuple :: (Int, Int) -> (Int, Int)
negateTuple (x, y) = (-x, -y)
dupe :: a -> (a, a)
dupe val = (val, val)