advent-of-code/2023/haskell/solutions/Day07.hs

99 lines
2.7 KiB
Haskell
Raw Normal View History

2023-12-07 20:54:26 +01:00
{-# LANGUAGE OverloadedStrings #-}
module Day07 (parse, solveA, solveB) where
import Data.Void (Void)
import Data.Char (isDigit, digitToInt)
import Data.Text (Text)
import Data.List (sort, find, sortBy)
import Data.Function (on)
2023-12-07 21:46:05 +01:00
import Data.Bifunctor (first, bimap, second)
import Control.Monad (join)
2023-12-07 20:54:26 +01:00
import Text.Megaparsec (Parsec, errorBundlePretty, runParser, many, eof)
import Text.Megaparsec.Char (alphaNumChar, space)
import Text.Megaparsec.Char.Lexer (decimal)
data Card = X | Plain Int | T | J | Q | K | A
deriving (Eq, Ord, Read, Show)
type Hand = [Card]
type Play = (Hand, Int)
data HandType = High | OnePair | TwoPair | Three | Full | Four | Five
deriving (Eq, Ord, Show)
type Parser = Parsec Void Text
parse :: Text -> Either String [Play]
parse = first errorBundlePretty . runParser plays ""
where
card :: Char -> Card
card c
2023-12-07 21:46:05 +01:00
| isDigit c = Plain . digitToInt $ c
| otherwise = read . pure $ c
2023-12-07 20:54:26 +01:00
play :: Parser Play
play = do
cs <- map card <$> many alphaNumChar
space
bet <- decimal
space
return (cs, bet)
plays :: Parser [Play]
plays = many play <* eof
spansBy :: (a -> a -> Bool) -> [a] -> [[a]]
2023-12-07 21:46:05 +01:00
spansBy p = helper []
2023-12-07 20:54:26 +01:00
where
2023-12-07 21:46:05 +01:00
helper acc [] = reverse . map reverse $ acc
helper [] (x:xs) = helper [[x]] xs
helper acc@(a:as) (x:xs) =
2023-12-07 20:54:26 +01:00
if p (head a) x
2023-12-07 21:46:05 +01:00
then helper ((x:a):as) xs
else helper ([x]:acc) xs
2023-12-07 20:54:26 +01:00
group :: Hand -> [[Card]]
group = spansBy (==) . sort
classify :: Hand -> HandType
2023-12-07 21:46:05 +01:00
classify = cast . sortBy (flip compare) . map length . group
where
cast :: [Int] -> HandType
cast ns = case ns of
5:_ -> Five
4:_ -> Four
3:2:_ -> Full
3:_ -> Three
2:2:_ -> TwoPair
2:_ -> OnePair
_ -> High
withType :: (Hand -> HandType) -> Play -> (HandType, Play)
withType toType = first (toType . fst) . join (,)
2023-12-07 20:54:26 +01:00
score :: Int -> Play -> Int
score = flip $ (*) . snd
solveA :: [Play] -> Int
2023-12-07 21:46:05 +01:00
solveA = sum . zipWith score [1..] . map snd . sort . map (withType classify)
2023-12-07 20:54:26 +01:00
2023-12-07 21:46:05 +01:00
classify' :: Hand -> HandType
2023-12-07 20:54:26 +01:00
classify' cs =
2023-12-07 21:46:05 +01:00
let counts = map (bimap length head . join (,)) . group $ cs
2023-12-07 20:54:26 +01:00
jokers = maybe 0 fst . find ((J ==) . snd) $ counts
others = sortBy (flip compare `on` fst) . filter ((J /=) . snd) $ counts
in case others of
[] -> Five
((n, c) : rest) -> classify . concatMap (uncurry replicate) $ (n + jokers, c) : rest
erase :: Card -> [Card] -> [Card]
erase e = helper
where
helper [] = []
helper (c:cs)
| e == c = X : helper cs
| otherwise = c : helper cs
solveB :: [Play] -> Int
2023-12-07 21:46:05 +01:00
solveB = sum . zipWith score [1..] . map snd . sort . map (second (first $ erase J) . withType classify')