1 -- Writeup at https://work.njae.me.uk/2022/12/14/advent-of-code-2022-day-12/
8 import Linear (V2(..), (^+^))
9 import Data.Array.IArray
11 type Position = V2 Int -- r, c
12 type Engine = Array Position Char
13 type NumPos = [Position]
16 NumberSeek { inNumber :: Bool
18 , foundNumbers :: [NumPos]
24 do dataFileName <- getDataFileName
25 text <- readFile dataFileName
26 let engine = mkEngine text
27 let allNums = findNumbers engine
28 let symbols = findSymbols engine
29 let partNums = filter (touchRegion symbols) allNums
32 -- print $ findNumbers engine
33 print $ part1 engine partNums
34 print $ part2 engine partNums
37 part1, part2 :: Engine -> [NumPos] -> Int
38 part1 engine partNums = sum partNumValues
39 where partNumValues = map (readNumber engine) partNums
41 part2 engine partNums = sum $ fmap product gearRatios
42 where stars = findStars engine
43 touchingStars = fmap (possibleGears partNums) stars
44 gears = filter ((==) 2 . length) touchingStars
45 gearRatios = [[readNumber engine n | n <- pNs] | pNs <- gears]
47 mkEngine :: String -> Engine
49 where rows = lines text
51 c = (length $ head rows) - 1
52 grid = listArray ((V2 0 0), (V2 r c)) $ concat rows
54 isEngineSymbol :: Char -> Bool
55 isEngineSymbol c = (not $ isDigit c) && (c /= '.')
57 findNumbers :: Engine -> [NumPos]
58 findNumbers engine = numbers
59 where ((V2 r1 _), (V2 r2 _)) = bounds engine
61 numbers = concatMap (foundNumbers . (findNumbersInRow engine)) rows
63 findNumbersInRow :: Engine -> Int -> NumberSeek
64 findNumbersInRow engine r
65 | inNumber finalSeek = NumberSeek False [] ((reverse $ positions finalSeek):foundNumbers finalSeek)
66 | otherwise = finalSeek
67 where finalSeek = foldl' (buildNumber engine)
68 (NumberSeek False [] [])
69 $ range $ rowBounds engine r
71 buildNumber :: Engine -> NumberSeek -> Position -> NumberSeek
72 buildNumber engine NumberSeek{..} p
73 | inNumber && isDigit c = NumberSeek True (p:positions) foundNumbers
74 | inNumber && not (isDigit c) = NumberSeek False [] ((reverse positions):foundNumbers)
75 | not inNumber && isDigit c = NumberSeek True [p] foundNumbers
76 | otherwise = NumberSeek False [] foundNumbers
79 rowBounds :: Engine -> Int -> (V2 Int, V2 Int)
80 rowBounds engine r = (V2 r c1, V2 r c2)
81 where ((V2 _ c1), (V2 _ c2)) = bounds engine
83 neighbours :: Position -> [Position]
84 neighbours p = [p ^+^ V2 dr dc | dr <- [-1..1], dc <- [-1..1]
85 , (dr, dc) /= (0, 0) ]
88 touchPoint :: [Position] -> Position -> Bool
89 touchPoint region point = not $ null $ intersect region $ neighbours point
91 touchRegion :: [Position] -> [Position] -> Bool
92 touchRegion region1 region2 = any (touchPoint region2) region1
95 readNumber :: Engine -> NumPos -> Int
96 readNumber engine ps = read $ map (engine !) ps
98 findSymbols :: Engine -> [Position]
99 findSymbols engine = filter (isEngineSymbol . (engine !)) $ indices engine
101 findStars :: Engine -> [Position]
102 findStars engine = filter ((==) '*' . (engine !)) $ indices engine
104 possibleGears :: [NumPos] -> Position -> [NumPos]
105 possibleGears nums star = filter (flip touchPoint star) nums