We parse the grid and then step through a simulation of the tachyon beam's spread and count the number of splitters that the beam actually hit.
166 lines
4.7 KiB
Haskell
166 lines
4.7 KiB
Haskell
{-# LANGUAGE LambdaCase #-}
|
|
{-# LANGUAGE RecordWildCards #-}
|
|
|
|
module Main where
|
|
|
|
import Data.Function (on)
|
|
import Data.Ix (range)
|
|
import Data.List (foldl', groupBy, sort, sortBy)
|
|
import Data.Map (Map)
|
|
import Data.Map qualified as M
|
|
import Data.Maybe (mapMaybe)
|
|
import Debug.Trace
|
|
import System.Environment (getArgs)
|
|
|
|
main :: IO ()
|
|
main = do
|
|
input <- readFile . head =<< getArgs
|
|
putStrLn $ unwords ["Part 1:", show $ part1 input]
|
|
putStrLn $ unwords ["Part 2:", show $ part2 input]
|
|
|
|
data Cell = Empty | Start | Splitter Int | Beam
|
|
deriving (Eq, Show)
|
|
|
|
instance Read Cell where
|
|
readsPrec _ "" = []
|
|
readsPrec _ (c : rest) = case c of
|
|
'.' -> [(Empty, rest)]
|
|
'S' -> [(Start, rest)]
|
|
'^' -> [(Splitter 0, rest)]
|
|
'|' -> [(Beam, rest)]
|
|
_ -> []
|
|
readList s = case readsPrec @Cell 0 s of
|
|
[(c, rest)] -> case readList rest of
|
|
[] -> [([c], rest)]
|
|
[(acc, rest')] -> [(c : acc, rest')]
|
|
[] -> []
|
|
|
|
data Grid = Grid
|
|
{ cells :: Map (Int, Int) Cell,
|
|
size :: (Int, Int),
|
|
currentBeams :: [(Int, Int)],
|
|
paths :: Int
|
|
}
|
|
deriving (Show)
|
|
|
|
showGrid :: Grid -> String
|
|
showGrid (Grid cs (rows, cols) _ _) =
|
|
unlines
|
|
. map (foldl' ((<>)) mempty . map showCell . (\r -> map (\c -> cs M.! (r, c)) [0 .. cols]))
|
|
$ [0 .. rows]
|
|
where
|
|
showCell c = show $ case c of
|
|
Empty -> '.'
|
|
Start -> 'S'
|
|
Splitter _ -> '^'
|
|
Beam -> '|'
|
|
|
|
instance Read Grid where
|
|
readsPrec _ =
|
|
(\cs -> [(Grid (M.fromList cs) (size cs) (start (cs)) 1, "")])
|
|
. concat
|
|
. map parseLine
|
|
. zip [0 ..]
|
|
. lines
|
|
where
|
|
start :: [((Int, Int), Cell)] -> [(Int, Int)]
|
|
start = map fst . filter (\(_, c) -> c == Start)
|
|
|
|
size :: [((Int, Int), Cell)] -> (Int, Int)
|
|
size cs = fst $ last cs
|
|
|
|
parseLine :: (Int, String) -> [((Int, Int), Cell)]
|
|
parseLine (r, line) = zipWith (\c cell -> ((r, c), cell)) [0 ..] . fst . head $ readList @Cell line
|
|
|
|
step :: Grid -> Grid
|
|
step g = foldl' go (g {currentBeams = []}) (filter valid $ currentBeams g)
|
|
where
|
|
go :: Grid -> ((Int, Int)) -> Grid
|
|
go g (row, col) = case cells g M.! (row, col) of
|
|
Empty ->
|
|
g
|
|
{ cells = M.adjust (const Beam) (row, col) (cells g),
|
|
currentBeams = (row + 1, col) : currentBeams g
|
|
}
|
|
Splitter 0 ->
|
|
g
|
|
{ cells = M.adjust (const $ Splitter 1) (row, col) (cells g),
|
|
currentBeams = (row, col + 1) : (row, col - 1) : currentBeams g,
|
|
paths = paths g + 1
|
|
}
|
|
Splitter n -> g {cells = M.adjust (const $ Splitter (n + 1)) (row, col) (cells g), paths = paths g + n}
|
|
Beam -> g {currentBeams = (row + 1, col) : currentBeams g}
|
|
Start -> g {currentBeams = (row + 1, col) : currentBeams g}
|
|
|
|
valid :: (Int, Int) -> Bool
|
|
valid p = p < size g
|
|
|
|
incrementCounter :: Cell -> Cell
|
|
incrementCounter (Splitter n) = Splitter (n + 1)
|
|
|
|
part1 :: String -> Int
|
|
part1 = M.size . M.filter nonZero . cells . final . read
|
|
where
|
|
nonZero :: Cell -> Bool
|
|
nonZero (Splitter n) = n > 0
|
|
nonZero _ = False
|
|
|
|
final :: Grid -> Grid
|
|
final gr =
|
|
let gr' = step gr
|
|
in if null (currentBeams gr') then gr' else final gr'
|
|
|
|
part2 :: String -> Int
|
|
part2 = (\g -> go g mempty) . read
|
|
where
|
|
go :: Grid -> Map (Int, Int) Int -> Int
|
|
go g acc = snd . M.findMin . foldl' (foldl' (onSplitter g)) acc $ splitters g
|
|
|
|
splitters :: Grid -> [[(Int, Int)]]
|
|
splitters =
|
|
groupBy ((==) `on` fst)
|
|
. sortBy (flip (compare `on` fst))
|
|
. M.keys
|
|
. M.filter
|
|
isSplitter
|
|
. cells
|
|
|
|
-- onSplitter sums the number of descendants in each leg of the
|
|
-- splitter and stores it in acc
|
|
onSplitter :: Grid -> Map (Int, Int) Int -> (Int, Int) -> Map (Int, Int) Int
|
|
onSplitter g acc (r, c) =
|
|
let h c = halfSplitter c (r + 1, fst . size $ g) acc
|
|
in M.insert (r, c) (sum . map (h . ($ c)) $ [succ, pred]) acc
|
|
|
|
halfSplitter :: Int -> (Int, Int) -> Map (Int, Int) Int -> Int
|
|
halfSplitter c range acc = case col c range acc of
|
|
[] -> 1
|
|
(n : _) -> n
|
|
|
|
-- col returns all elements in a column c
|
|
col :: Int -> (Int, Int) -> Map (Int, Int) a -> [a]
|
|
col c rows g = mapMaybe ((g M.!?) . (,c)) $ range rows
|
|
|
|
isSplitter (Splitter _) = True
|
|
isSplitter _ = False
|
|
|
|
testInput :: String
|
|
testInput =
|
|
unlines
|
|
[ ".......S.......",
|
|
"...............",
|
|
".......^.......",
|
|
"...............",
|
|
"......^.^......",
|
|
"...............",
|
|
".....^.^.^.....",
|
|
"...............",
|
|
"....^.^...^....",
|
|
"...............",
|
|
"...^.^...^.^...",
|
|
"...............",
|
|
"..^...^.....^..",
|
|
"...............",
|
|
".^.^.^.^.^...^.",
|
|
"..............."
|
|
]
|