#!/usr/bin/env nix-shell #!nix-shell --pure -i "runghc -- -i../" -p "haskellPackages.ghcWithPackages (pkgs: with pkgs; [ parallel ])" import Aoc (readAndParseStdin) import Control.Parallel (par, pseq) import Data.Array import Data.IntSet (IntSet) import qualified Data.IntSet as IntSet import Text.Parsec (char, choice, endBy, many1) import Text.Parsec.String (Parser) main = do input <- readAndParseStdin parseGrid print $ part1 input print $ part2 input part1 :: [[Path]] -> Int part1 = walkMazeH True part2 :: [[Path]] -> Int part2 = walkMazeH False walkMazeH :: Bool -> [[Path]] -> Int walkMazeH slippery input = maximum $ map (subtract 1 . IntSet.size) $ fromDList $ walkMaze (1, 0) (IntSet.singleton $ hashCoord (1, 0)) where maxX = length (head input) maxY = length input exit = (maxX - 2, maxY - 1) grid = array ((0, 0), (maxX, maxY)) [((x, y), input !! y !! x) | y <- [0 .. maxY - 1], x <- [0 .. maxX - 1]] hashCoord (x, y) = x * 1000 + y walkMaze :: (Int, Int) -> IntSet -> DList IntSet walkMaze pos@(x, y) cacc | pos == exit = toDList [newCacc] | slippery = case grid ! (x, y) of Path -> parallelTraverseAll Forest -> error "invalid state" UpSlope -> parallelTraverse (x, y - 1) RightSlope -> parallelTraverse (x + 1, y) DownSlope -> parallelTraverse (x, y + 1) LeftSlope -> parallelTraverse (x - 1, y) | otherwise = parallelTraverseAll where newCacc = IntSet.insert (hashCoord pos) cacc isValidPath pos@(x, y) = y >= 0 && x >= 0 && y < maxY && x < maxX && IntSet.notMember (hashCoord pos) cacc && (grid ! pos) /= Forest parallelTraverseAll = foldr parallelWalk (toDList []) $ filter isValidPath [(x, y - 1), (x + 1, y), (x, y + 1), (x - 1, y)] parallelWalk path acc = go path `par` (acc `pseq` (acc `dappend` go path)) parallelTraverse pos = if isValidPath pos then go pos else toDList [] go pos = walkMaze pos newCacc type DList a = [a] -> [a] {-# INLINEABLE toDList #-} toDList :: [a] -> DList a toDList xs = (xs ++) {-# INLINEABLE fromDList #-} fromDList :: DList a -> [a] fromDList dl = dl [] {-# INLINEABLE dappend #-} dappend :: DList a -> DList a -> DList a dappend dl1 dl2 = dl1 . dl2 parseGrid :: Parser [[Path]] parseGrid = parseGridRow `endBy` char '\n' where parseGridRow = many1 parseGridTile parseGridTile = choice [ Path <$ char '.', Forest <$ char '#', UpSlope <$ char '^', RightSlope <$ char '>', DownSlope <$ char 'v', LeftSlope <$ char '<' ] data Path = Path | Forest | UpSlope | RightSlope | DownSlope | LeftSlope deriving (Enum, Show, Eq)