Advent of Code 2021, Day 4
Our submarine adventure continues, but it’s getting boring so we’re gonna play some bingo with a giant squid.
That’s the only SFW squid gif I could find. Without further ado, here we go.
Since I was lazy yesterday, I’ll be lazy again today, but in Haskell.
We’re going to be a bit fancy today and write a simple parser with megaparsec:
data NavCom = Forward Integer | Up Integer | Down Integer deriving (Eq, Show) type Position = (Integer, Integer, Integer) -- Parser type Parser = Parsec Void String sc :: Parser () sc = L.space space1 empty empty lexeme :: Parser a -> Parser a lexeme = L.lexeme sc integer :: Parser Integer integer = lexeme L.decimal navcom :: Parser NavCom navcom = operator <*> integer where operator = lexeme $ choice [ Forward <$ string' "forward" , Up <$ string' "up" , Down <$ string' "down" ] navcoms :: Parser [NavCom] navcoms = manyTill navcom eof
First, we use some algebraic datatypes to define our own types. This will come in handy in a moment when we use pattern matching. Navigation commands are its own type, positions will be represented as triples.
The parser itself,
navcoms, is pretty simple. We use a space consumer
sc that will take care of all whitespace.
lexeme is a helper function that will remove all whitespace after a token has been matched.
navcom finally puts it all together: We choose the correct type constructor by matching the input with either one of the strings
down, and apply that to the
integer parser who will parse the integer after the just matched string.
As always, we hide the nasty impure input the
IO () monad
main :: IO () main = do handle <- openFile "Input.txt" ReadMode contents <- hGetContents handle let (Right input) = runParser navcoms "" contents print $ "Part 1: " ++ show (part1 input) print $ "Part 2: " ++ show (part2 input) hClose handle
Only thing missing now are the
We’ll use a similar approach as with the Scheme solution. We define a few simple functions and compose them into a solution:
moveSimple :: NavCom -> Position -> Position moveSimple (Forward val) (horizontal, depth, aim) = (horizontal + val, depth, aim) moveSimple (Up val) (horizontal, depth, aim) = (horizontal, depth - val, aim) moveSimple (Down val) (horizontal, depth, aim) = (horizontal, depth + val, aim) navigate :: (NavCom -> Position -> Position) -> [NavCom] -> Position navigate move = foldr move (0, 0, 0) solution :: (NavCom -> Position -> Position) -> [NavCom] -> Integer solution movef navcoms = let navcoms' = reverse navcoms (horizontal, depth, _) = navigate movef navcoms' in horizontal * depth part1 :: [NavCom] -> Integer part1 = solution moveSimple
Here, we use pattern matching over the types we defined earlier. Then we use a simple
foldr over the list of navigation commands to get the final position in
navigate. That result is then used in
solution to again calculate the product of the horizontal position and the depth. We can compose everything in
part1. Take note of the eta-reduction applied to
We can reuse our solution from part 1:
moveAim :: NavCom -> Position -> Position moveAim (Forward val) (horizontal, depth, aim) = (horizontal + val, depth + aim * val, aim) moveAim (Up val) (horizontal, depth, aim) = (horizontal, depth, aim - val) moveAim (Down val) (horizontal, depth, aim) = (horizontal, depth, aim + val) part2 :: [NavCom] -> Integer part2 = solution moveAim
We again only change the move function we pass to
solution to get the correct result:
$ ghci .\Dive_Bang.hs GHCi, version 9.0.1: https://www.haskell.org/ghc/ :? for help [1 of 1] Compiling DiveBang ( Dive_Bang.hs, interpreted ) Ok, one module loaded. ghci> main "Part 1: 1815044" "Part 2: 1739283308" ghci>
Again, the correct numbers for my input.
That’s it for today. You can find all my solutions on my Github.
See you tomorrow.