2 import qualified Data.Text.IO as TIO
4 import Data.Attoparsec.Text
5 import Control.Applicative
10 type BingoSquare = [[BingoNum]]
11 data BingoNum = BingoNum Int Bool
13 data BingoState = BingoState Int [BingoSquare]
20 do text <- TIO.readFile "data/advent04.txt"
21 let (nums, rawSquares) = successfulParse text
22 let squares = map mkSquare rawSquares
23 print $ part1 nums squares
24 print $ part2 nums squares
26 part1 :: [Int] -> [BingoSquare] -> Int
27 part1 callNums squares = finalCalled * winningSum
28 where allSteps = scanl' bingoStep (BingoState 0 squares) callNums
29 BingoState finalCalled finalSquares = head $ dropWhile (not . hasCompletedSquare) allSteps
30 winningSquare = head $ filter completed finalSquares
31 winningSum = unmarkedSum winningSquare
33 part2 :: [Int] -> [BingoSquare] -> Int
34 part2 callNums squares = finalCalled * winningSum
35 where allSteps = scanl' pruningBingoStep (BingoState 0 squares) callNums
36 BingoState finalCalled finalSquares =
37 head $ dropWhile (not . hasCompletedSquare)
38 $ dropWhile manyRemainingSquares allSteps
39 winningSquare = head finalSquares
40 winningSum = unmarkedSum winningSquare
43 bingoStep :: BingoState -> Int -> BingoState
44 bingoStep (BingoState _ squares) caller = BingoState caller squares'
45 where squares' = map (callSquare caller) squares
47 pruningBingoStep :: BingoState -> Int -> BingoState
48 pruningBingoStep (BingoState _ squares) caller = BingoState caller squares''
49 where squares' = filter (not . completed) squares
50 squares'' = map (callSquare caller) squares'
52 hasCompletedSquare :: BingoState -> Bool
53 hasCompletedSquare (BingoState _n squares) = any completed squares
56 unmarkedSum :: BingoSquare -> Int
57 unmarkedSum bingoSquare =
58 sum [value bn | r <- bingoSquare, bn <- r, (not $ isCalled bn)]
60 manyRemainingSquares :: BingoState -> Bool
61 manyRemainingSquares (BingoState _ squares) = (length squares) > 1
64 mkBingoNum :: Int -> BingoNum
65 mkBingoNum n = BingoNum n False
67 forceCall :: BingoNum -> BingoNum
68 forceCall (BingoNum n _) = BingoNum n True
70 call :: Int -> BingoNum -> BingoNum
71 call num (BingoNum target called)
72 | num == target = BingoNum target True
73 | otherwise = BingoNum target called
75 isCalled :: BingoNum -> Bool
76 isCalled (BingoNum _ c) = c
78 value :: BingoNum -> Int
79 value (BingoNum n _) = n
81 mkSquare :: Square -> BingoSquare
82 mkSquare = map (map mkBingoNum)
84 callSquare :: Int -> BingoSquare -> BingoSquare
85 callSquare n = map (map (call n))
87 completed :: BingoSquare -> Bool
88 completed sq = (any completedRow sq) || (any completedRow $ transpose sq)
90 completedRow :: [BingoNum] -> Bool
91 completedRow = all isCalled
93 -- Parse the input file
95 bingoP = (,) <$> calledP <*> (blankLines *> squaresP)
97 calledP = decimal `sepBy` ","
99 squaresP = squareP `sepBy` blankLines
100 squareP = rowP `sepBy` endOfLine
101 rowP = paddedDecimal `sepBy1` " "
103 -- paddedDecimal :: Parser Text Int
104 paddedDecimal = (many " ") *> decimal
106 blankLines = many1 endOfLine
108 -- successfulParse :: Text -> (Integer, [Maybe Integer])
109 successfulParse input =
110 case parseOnly bingoP input of
111 Left _err -> ([], []) -- TIO.putStr $ T.pack $ parseErrorPretty err