X-Git-Url: https://git.njae.me.uk/?a=blobdiff_plain;f=src%2Fadvent24%2Fadvent24.hs;fp=src%2Fadvent24%2Fadvent24.hs;h=df3136b9e3e138c49b300c9e6a45290cdc9242c6;hb=11ba4341db1d4ae5d0c0fcc966b1e03759ea36fe;hp=0000000000000000000000000000000000000000;hpb=d182cea942b52e5522022adf376011c74d3942e7;p=advent-of-code-17.git diff --git a/src/advent24/advent24.hs b/src/advent24/advent24.hs new file mode 100644 index 0000000..df3136b --- /dev/null +++ b/src/advent24/advent24.hs @@ -0,0 +1,114 @@ +{-# LANGUAGE NegativeLiterals #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeFamilies #-} + +import Data.Text (Text) +import qualified Data.Text as T +import qualified Data.Text.IO as TIO + +import Text.Megaparsec hiding (State) +import qualified Text.Megaparsec.Lexer as L +import Text.Megaparsec.Text (Parser) +import qualified Control.Applicative as CA + +import qualified Data.MultiSet as B -- B for bag +import qualified Data.Set as S +import Data.Either + +type Part = B.MultiSet Integer +type Parts = B.MultiSet Part +type Candidates = S.Set Part +data Bridge = Bridge { bridgeParts :: Parts, requiring :: Integer } deriving (Eq, Show, Ord) +type Bridges = S.Set Bridge + + +main :: IO () +main = do + text <- TIO.readFile "data/advent24.txt" + let parts = successfulParse text + let bridges = allBridges parts + print $ part1 bridges + print $ part2 bridges + + +part1 = strongestBridge + +part2 = bestBridge + +strongestBridge :: Bridges -> Integer +strongestBridge bridges = S.findMax $ S.map bridgeStrength bridges + +bestBridge :: Bridges -> Integer +bestBridge bridges = strongestBridge longBridges + where longest = S.findMax $ S.map bridgeLength bridges + longBridges = S.filter (\b -> bridgeLength b == longest) bridges + + +emptyBridge :: Bridge +emptyBridge = Bridge { bridgeParts = B.empty, requiring = 0} + + +allBridges :: Parts -> Bridges +allBridges parts = extendBridges parts (S.singleton emptyBridge) S.empty + +extendBridges :: Parts -> Bridges -> Bridges -> Bridges +extendBridges parts bridges completed = + if S.null bridges then completed + else extendBridges parts bridges' completed' + where updates = map (extendOneBridge parts) $ S.toList bridges + newCompleted = lefts updates + completed' = S.union completed $ S.fromList newCompleted + bridges' = S.unions $ rights updates + +extendOneBridge :: Parts -> Bridge -> Either Bridge Bridges +extendOneBridge parts bridge = + if S.null $ candidates parts bridge + then Left bridge + else Right (S.map (grow bridge) $ candidates parts bridge) + +grow :: Bridge -> Part -> Bridge +grow bridge part = bridge {bridgeParts = bp', requiring = req'} + where req = requiring bridge + req' = B.findMin $ B.delete req part + bp' = B.insert part $ bridgeParts bridge + +candidates :: Parts -> Bridge -> Candidates +candidates parts bridge = B.toSet $ B.filter canUse parts + where needed = requiring bridge + canUse p = hasPort p needed && available parts p bridge + +hasPort :: Part -> Integer -> Bool +hasPort part port = port `B.member` part + +available :: Parts -> Part -> Bridge -> Bool +available parts part bridge = B.occur part parts > B.occur part (bridgeParts bridge) + + +bridgeStrength :: Bridge -> Integer +bridgeStrength bridge = B.fold (+) 0 $ B.map partStrength $ bridgeParts bridge + where partStrength = sum . B.elems + +bridgeLength :: Bridge -> Int +bridgeLength bridge = B.size $ bridgeParts bridge + + +-- really persuade Megaparsec not to include newlines in how it consume spaces. +onlySpace = (char ' ') <|> (char '\t') + +sc :: Parser () +sc = L.space (skipSome onlySpace) CA.empty CA.empty + +lexeme = L.lexeme sc +integer = lexeme L.integer +symbol = L.symbol sc +slash = symbol "/" + +partsP = partP `sepBy` newline +partP = B.fromList <$> integer `sepBy` slash + +successfulParse :: Text -> Parts +successfulParse input = + case parse partsP "input" input of + Left _error -> B.empty -- TIO.putStr $ T.pack $ parseErrorPretty err + Right partsList -> B.fromList partsList \ No newline at end of file