1 -- Writeup at https://work.njae.me.uk/2023/12/08/advent-of-code-2023-day-8/
4 import Data.Text (Text)
5 import qualified Data.Text.IO as TIO
6 import Data.Attoparsec.Text -- hiding (take)
7 import Control.Applicative
9 import qualified Data.Map.Strict as M
11 import Control.Monad.State.Strict
13 data Spring = Unknown | Damaged | Operational deriving (Show, Eq)
14 data Record = Record [Spring] [Int] deriving (Show)
16 type Cache = M.Map Record Int
17 -- type CacheState = State Cache
22 do dataFileName <- getDataFileName
23 text <- TIO.readFile dataFileName
24 let records = successfulParse text
26 -- print $ fmap numDamagedToPlace records
27 -- print $ fmap candidates records
28 -- print $ possibleAssignments (records !! 1)
29 -- print $ fmap countViableAssignments records
31 print $ unfoldRecord $ head records
34 part1, part2 :: [Record] -> Int
35 part1 = sum . fmap countViableAssignments
36 part2 = sum . fmap (countViableAssignments . unfoldRecord)
38 unfoldRecord :: Record -> Record
39 unfoldRecord (Record springs signature) = Record uSprings uSignature
40 where uSprings = intercalate [Unknown] $ replicate 5 springs
41 uSignature = concat $ replicate 5 signature
44 countViable :: Record -> CacheState Int
47 countViable previousSprings (Record (s:springs) (g:signature)) =
50 -- if next few springs can match next group:
51 -- countViable (springs after group) (tail groups) + countViable (tail springs) groups
53 -- countViable (tail springs) groups
57 countViableAssignments :: Record -> Int
58 countViableAssignments = length . filter matchesSignature . possibleAssignments
60 matchesSignature :: Record -> Bool
61 matchesSignature (Record springs signature) = signSprings springs == signature
63 signSprings :: [Spring] -> [Int]
64 signSprings = fmap (length) . filter ((== Damaged) . head) . group
66 choose :: Int -> [a] -> [[a]]
69 | length xs == n - 1 = [(x:xs)]
70 | otherwise = (fmap (x:) (choose (n-1) xs)) ++ (choose n xs)
72 -- unknownIndices :: [Spring] -> [Int]
73 -- unknownIndices = elemIndices Unknown
75 numDamagedToPlace :: Record -> Int
76 numDamagedToPlace (Record springs signature) = totalDamaged - knownDamaged
77 where knownDamaged = length $ filter (== Damaged) springs
78 totalDamaged = sum signature
80 candidates :: Record -> [[Int]]
81 candidates r@(Record springs _) =
82 choose (numDamagedToPlace r) (elemIndices Unknown springs)
84 replaceUnknowns :: [Spring] -> [Int] -> [Spring]
85 replaceUnknowns springs indices = foldr go [] indexedSprings
86 where indexedSprings = zip [0..] springs
87 go (i, Unknown) acc = if (i `elem` indices) then Damaged:acc
91 possibleAssignments :: Record -> [Record]
92 possibleAssignments r@(Record springs signature) =
93 fmap (\p -> Record p signature) possibles
94 where cands = candidates r
95 possibles = fmap (replaceUnknowns springs) cands
97 -- Parse the input file
99 recordsP :: Parser [Record]
100 recordP :: Parser Record
101 springP :: Parser Spring
103 recordsP = recordP `sepBy` endOfLine
104 recordP = Record <$> (many1 springP <* " ") <*> (decimal `sepBy` ",")
105 springP = (Unknown <$ "?") <|> (Damaged <$ "#") <|> (Operational <$ ".")
107 successfulParse :: Text -> [Record]
108 successfulParse input =
109 case parseOnly recordsP input of
110 Left _err -> [] -- TIO.putStr $ T.pack $ parseErrorPretty err
111 Right matches -> matches