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')
|