-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathd07-b.hs
More file actions
75 lines (61 loc) · 2.54 KB
/
Copy pathd07-b.hs
File metadata and controls
75 lines (61 loc) · 2.54 KB
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
-- Solution idea: Backward induction on DAG.
import qualified Data.IntMap.Strict as IM
import qualified Data.Map.Strict as M
import Data.List (sort, sortOn, find)
import Data.Ord (Down(..))
type Coord = (Int, Int)
type ColumnMap = IM.IntMap [Int] -- Column -> Sorted list of Rows
type Cache = M.Map Coord Integer -- Splitter Coord -> Number of Timelines
-- Raycaster
-- Given starting position (r, c), find first splitter directly below it.
findHit :: ColumnMap -> Coord -> Maybe Coord
findHit splitters (r, c) =
case IM.lookup c splitters of
Nothing -> Nothing
Just rows ->
-- Find the smallest row r' > r
case find (> r) rows of
Nothing -> Nothing -- Beam exits the manifold
Just hitR -> Just (hitR, c)
-- Backward Induction
solve :: String -> Integer
solve input =
let
rows = lines input
cells = [ ((r, c), char)
| (r, row) <- zip [0..] rows
, (c, char) <- zip [0..] row
, char == 'S' || char == '^'
]
startPos = fst $ head $ filter ((== 'S') . snd) cells
splitterCoords = map fst $ filter ((== '^') . snd) cells
-- Build Sparse Column Index
colMap = IM.fromListWith (++) [ (c, [r]) | (r, c) <- splitterCoords ]
sortedColMap = IM.map sort colMap
-- Sort splitters Bottom-Up
-- -> always process children before parents
orderedSplitters = sortOn (Down . fst) splitterCoords
-- timeline count for each splitter
calcTimelines :: Cache -> Coord -> Cache
calcTimelines cache (r, c) =
let
leftHit = findHit sortedColMap (r, c - 1)
rightHit = findHit sortedColMap (r, c + 1)
-- Value lookup helper:
-- If hit is Nothing (exit), count is 1.
-- If hit is Just node, look it up in cache.
val hit = case hit of
Nothing -> 1
Just h -> cache M.! h
total = val leftHit + val rightHit
in M.insert (r, c) total cache
-- Backward Induction, kinda
finalCache = foldl calcTimelines M.empty orderedSplitters
-- Finally, trace the path from Source S
startHit = findHit sortedColMap startPos
in case startHit of
Nothing -> 1 -- S points straight to exit
Just h -> finalCache M.! h
main :: IO ()
main = do
interact $ show . solve