adventofcode-2021/4/bingo.hs

122 lines
3.9 KiB
Haskell

import Control.Exception (assert)
import Data.List (elemIndices, groupBy, maximumBy, minimumBy, transpose)
import Data.Maybe (isJust, listToMaybe)
import Data.Ord (comparing)
import Debug.Trace (traceShowId)
main = do
bingoInput <- getContents
putStrLn (solution1 bingoInput)
putStrLn (solution2 bingoInput)
solution1 :: String -> String
solution1 = show . scoreBingo . shortestBingo . parseBingos
solution2 :: String -> String
solution2 = show . scoreBingo . longestBingo . parseBingos
parseBingos :: String -> ([Int], [[[Int]]])
parseBingos input = (calls, cards)
where
(callStrings, bingoStrings) = splitAt 2 (lines input)
calls :: [Int]
calls = map read ((wordsWhen (==',')) (head callStrings))
cards :: [[[Int]]]
cards = chunksToNumbers (chunkByEmpty bingoStrings)
where
lineToNumbers :: String -> [Int]
lineToNumbers = (map read) . words
linesToNumbers :: [String] -> [[Int]]
linesToNumbers = map lineToNumbers
chunksToNumbers :: [[String]] -> [[[Int]]]
chunksToNumbers = map linesToNumbers
longestBingo :: ([Int], [[[Int]]]) -> ([Int], [Int])
longestBingo (calls, cards) =
maximumBy (comparing bingoLength) solvedBingos
where
bingoLength :: ([Int], [Int]) -> Int
bingoLength (start, _) = length start
solvedBingos :: [([Int], [Int])]
solvedBingos = map (uncurry solveBingo) (zip (repeat calls) cards)
shortestBingo :: ([Int], [[[Int]]]) -> ([Int], [Int])
shortestBingo (calls, cards) =
minimumBy (comparing bingoLength) solvedBingos
where
bingoLength :: ([Int], [Int]) -> Int
bingoLength (start, _) = length start
solvedBingos :: [([Int], [Int])]
solvedBingos = map (uncurry solveBingo) (zip (repeat calls) cards)
solveBingo :: [Int] -> [[Int]] -> ([Int], [Int])
solveBingo calls card =
(marked, unmarked)
where
unmarked = filter (\i -> isJust (elem2dIndex i card)) remaining
(marked, remaining) = splitAt (length (fst (indices calls card))+1) calls
indices :: [Int] -> [[Int]] -> ([Int], [Int])
indices calls cards =
break (\call -> isSolved (take (call+1) calls) (elem2dIndex (calls !! call) card)) [0..length(calls)-1]
isSolved :: [Int] -> Maybe (Int, Int) -> Bool
isSolved _ Nothing = False
isSolved calls (Just (y, x)) =
(isSublist (card !! x) calls)
|| (isSublist ((transpose card) !! y) calls)
scoreBingo :: ([Int], [Int]) -> Int
scoreBingo (marked, unmarked) = (last marked) * (sum unmarked)
isSublist :: (Eq a) => [a] -> [a] -> Bool
isSublist a b = all (`elem` b) a
elem2dIndex :: (Eq a) => a -> [[a]] -> Maybe (Int, Int)
elem2dIndex e m = listToMaybe [ (x,y) | (y,r) <- zip [0..] m, x <- elemIndices e r ]
wordsWhen :: (Char -> Bool) -> String -> [String]
wordsWhen p s = case dropWhile p s of
"" -> []
s' -> w : wordsWhen p s''
where (w, s'') = break p s'
chunkByEmpty :: [String] -> [[String]]
chunkByEmpty [] = []
chunkByEmpty (x:xs) = (if null x then chunk else (x:chunk)) : chunkByEmpty rest
where
(chunk,rest) = break (null) xs
-- Tests
testInput1 :: String
testInput1 = unlines [
"7,4,9,5,11,17,23,2,0,14,21,24,10,16,13,6,15,25,12,22,18,20,8,19,3,26,1",
"",
"22 13 17 11 0",
" 8 2 23 4 24",
"21 9 14 16 7",
" 6 10 3 18 5",
" 1 12 20 15 19",
"",
" 3 15 0 2 22",
" 9 18 13 17 5",
"19 8 7 25 23",
"20 11 10 24 4",
"14 21 16 12 6",
"",
"14 21 17 24 4",
"10 16 15 9 19",
"18 8 23 26 20",
"22 11 13 6 5",
" 2 0 12 3 7"
]
test1 :: String
test1 = assert ((solution1 testInput1) == "4512") "success"
test2 :: String
test2 = assert ((solution2 testInput1) == "1924") "success"
isSublistTest1 = isSublist [0, 1, 2] [0, 1, 2, 3]
isSublistTest2 = isSublist [0, 1, 2] [0, 1, 3]
isSublistTest3 = isSublist [0, 1, 3] [0, 1, 3]
isSublistTest4 = isSublist [0, 1, 3] [0, 1, 4, 3, 2]