never executed always true always false
    1 {-|
    2 Module:      Y2021.D04
    3 Description: Advent of Code 2021 Day 04 Solutions.
    4 License:     MIT
    5 Maintainer:  @tylerjl
    6 
    7 Solutions to the 2021 day 04 set of problems for <adventofcode.com>.
    8 -}
    9 module Y2021.D04 where
   10 
   11 import Data.Attoparsec.Text
   12 import Data.List (transpose)
   13 import Data.Maybe (isNothing, catMaybes)
   14 import Data.Monoid (Sum (Sum), getSum)
   15 import Data.Set (Set)
   16 import Data.Text (Text)
   17 import qualified Data.Set as S
   18 
   19 -- |Fancy data type to represent _game_ state, not just a card
   20 newtype Bingo a = Bingo a deriving Show
   21 -- |I'm not sure if a derive would get this right
   22 instance Functor Bingo where
   23   fmap f (Bingo a) = Bingo (f a)
   24 -- |Represents a game board state
   25 data Card = CardArr [[Square]]
   26           | CardSet RowSet ColSet
   27           deriving Show
   28 -- |Alternative game board representation
   29 type ColSet = Set (Set (Sum Int))
   30 type RowSet = Set (Set (Sum Int))
   31 -- |Small wrapper over how to record marked/unmarked squares
   32 type Square = Maybe (Sum Int)
   33 
   34 -- |Solve part A
   35 part4A :: Text -> Int
   36 part4A (bingoParser -> Right (ns, cs)) = solve4 head ns cs
   37 part4A (bingoParser -> Left err) = error err
   38 
   39 -- |Solve part A - with sets!
   40 part4ASet :: Text -> Int
   41 part4ASet (bingoParser -> Right (ns, map (fmap intoSet) -> cs)) =
   42   solve4 head ns cs
   43 part4ASet (bingoParser -> Left err) = error err
   44 
   45 -- |Solve part B
   46 part4B :: Text -> Int
   47 part4B (bingoParser -> Right (ns, cs)) = solve4 last ns cs
   48 part4B (bingoParser -> Left err) = error err
   49 
   50 -- |Solve part B - with sets!
   51 part4BSet :: Text -> Int
   52 part4BSet (bingoParser -> Right (ns, map (fmap intoSet) -> cs)) =
   53   solve4 last ns cs
   54 part4BSet (bingoParser -> Left err) = error err
   55 
   56 -- |Transform a bingo game from multidimensional-array based to a set-based
   57 -- game.
   58 intoSet :: Card -> Card
   59 intoSet (CardArr (map catMaybes -> rows)) = CardSet rows' cols'
   60   where rows' = S.fromList $ map S.fromList rows
   61         cols' = S.fromList $ map S.fromList (transpose rows)
   62 intoSet c@(CardSet _ _) = c
   63 
   64 -- |Fortunately both A and B are just asking slightly different questions, so we
   65 -- have a higher-order function to determine how to pull the matching value from
   66 -- our resultant list.
   67 solve4 :: ([(Int, Bingo Card)] -> (Int, Bingo Card)) -> [Int] -> [Bingo Card] -> Int
   68 solve4 f x = tally . f . iterateMap x
   69 
   70 -- |Problem-defined method of scoring a card. The "in-progress" scoring isn't
   71 -- actually ever used. Probably a bug.
   72 tally :: (Int, Bingo Card) -> Int
   73 tally (n, Bingo card)  = n * sumCard card
   74 
   75 -- |Yank out the total values for a card.
   76 sumCard :: Card -> Int
   77 sumCard (CardArr board) = (getSum . mconcat . catMaybes . concat) board
   78 sumCard (CardSet rows cols)
   79   | any S.null rows = sumSet rows
   80   | otherwise = sumSet cols
   81   where sumSet = getSum . S.fold mappend (Sum 0) . S.map (S.fold mappend (Sum 0))
   82 
   83 -- |Our main recursive loop to walk through the "called" numbers. We return a
   84 -- list solely of elements that meet the "ended" predicate.
   85 iterateMap :: [Int] -> [Bingo Card] -> [(Int, Bingo Card)]
   86 iterateMap [] _ = []
   87 iterateMap (n:nums) cards = zip (repeat n) needles ++ iterateMap nums haystacks
   88   where (needles, haystacks) = mark ([], []) n cards
   89 
   90 -- |Accept a list of `Bingo Card`s and update each with the called number.
   91 mark :: ([Bingo Card], [Bingo Card]) -> Int -> [Bingo Card] -> ([Bingo Card], [Bingo Card])
   92 mark acc _ [] = acc
   93 mark (l, r) num (card:cards)
   94   | gameEnd card' = mark (card' : l, r) num cards
   95   | otherwise = mark (l, card' : r) num cards
   96   where card' = markRows num card
   97 
   98 -- |Update a `Card` - we make the determination to "end" a game here, so as soon
   99 -- as its "won" the type swaps over to the ended value.
  100 markRows :: Int -> Bingo Card -> Bingo Card
  101 markRows n (Bingo (CardArr board)) = Bingo $ CardArr $ map (map (markRow n)) board
  102   where
  103     markRow _ Nothing = Nothing
  104     markRow n' a@(Just (Sum b)) | b == n'   = Nothing
  105                                 | otherwise = a
  106 markRows n (Bingo (CardSet rows cols)) = Bingo $ CardSet (markSet rows) (markSet cols)
  107   where markSet = S.map (S.delete (Sum n))
  108 
  109 -- |Important function to determine if a card is a winning card.
  110 gameEnd :: Bingo Card -> Bool
  111 gameEnd (Bingo (CardArr card))
  112   = check card || (check . transpose) card
  113   where check = any (all isNothing)
  114 gameEnd (Bingo (CardSet rows cols))
  115   = any S.null rows || any S.null cols
  116 
  117 -- |This is a sort of hairy, but all-in-one, parser for the problem set input. 5
  118 -- is a magic number for board size.
  119 bingoParser :: Text -> Either String ([Int], [Bingo Card])
  120 bingoParser = parseOnly parser
  121   where
  122     parser = (,) <$> (lottoParser <* endOfLine) <*> cardsParser <* atEnd
  123     lottoParser = decimal `sepBy1` char ',' <* endOfLine
  124     rowParser   = (Just . Sum <$> decimal) `sepBy1` many1 (char ' ')
  125     cardParser  = Bingo . CardArr <$>
  126       count 5 (many' space *> rowParser <* endOfLine)
  127     cardsParser = cardParser `sepBy1` endOfLine