{-# Language QuasiQuotes, TransformListComp, ParallelListComp #-}
{-|
Module      : Main
Description : Day 7 solution
Copyright   : (c) Eric Mertens, 2023
License     : ISC
Maintainer  : emertens@gmail.com

<https://adventofcode.com/2023/day/7>

Sort the hands of a poker-like card game and compute the
resulting winnings.

>>> :{
:main +
"32T3K 765
T55J5 684
KK677 28
KTJJT 220
QQQJA 483
"
:}
6440
5905

-}
module Main (main) where

import Advent (format, counts)
import Data.Foldable (toList)
import Data.List (sortOn, sort, elemIndex, nub)
import Data.Maybe (fromJust)

-- |
--
-- >>> :main
-- 248422077
-- 249817836
main :: IO ()
IO ()
main =
 do [([Char], Int)]
input <- [format|2023 7 (%s %d%n)*|]
    Int -> IO ()
forall a. Show a => a -> IO ()
print (([Char] -> [Int]) -> [([Char], Int)] -> Int
forall a. Ord a => ([Char] -> a) -> [([Char], Int)] -> Int
winnings [Char] -> [Int]
strength1 [([Char], Int)]
input)
    Int -> IO ()
forall a. Show a => a -> IO ()
print (([Char] -> [Int]) -> [([Char], Int)] -> Int
forall a. Ord a => ([Char] -> a) -> [([Char], Int)] -> Int
winnings [Char] -> [Int]
strength2 [([Char], Int)]
input)

-- | Compute the winnings after ordering the given hands by strength
-- and multiplying the bids by position in the ranked list.
winnings :: Ord a => (String -> a) -> [(String, Int)] -> Int
winnings :: forall a. Ord a => ([Char] -> a) -> [([Char], Int)] -> Int
winnings [Char] -> a
strength [([Char], Int)]
input =
  [Int] -> Int
forall a. Num a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum [Int
bid Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
rank | Int
rank        <- [Int
1..]
                  | ([Char]
hand, Int
bid) <- [([Char], Int)]
input, then (a -> a) -> [a] -> [a]
(([Char], Int) -> a) -> [([Char], Int)] -> [([Char], Int)]
forall {a}. (a -> a) -> [a] -> [a]
forall b a. Ord b => (a -> b) -> [a] -> [a]
sortOn by [Char] -> a
strength [Char]
hand]

-- | Map a hand to a representative of its strength for part 1
--
-- >>> strength1 "2AAAA" < strength1 "33332"
-- True
--
-- >>> strength1 "77788" < strength1 "77888"
-- True
--
-- >>> strength1 "KTJJT" < strength1 "KK677"
-- True
--
-- >>> strength1 "T55J5" < strength1 "QQQJA"
-- True
strength1 :: String -> [Int]
strength1 :: [Char] -> [Int]
strength1 [Char]
hand = [Char] -> Int
category [Char]
hand Int -> [Int] -> [Int]
forall a. a -> [a] -> [a]
: (Char -> Int) -> [Char] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map Char -> Int
val [Char]
hand
  where
    val :: Char -> Int
val Char
x = Maybe Int -> Int
forall a. HasCallStack => Maybe a -> a
fromJust (Char
x Char -> [Char] -> Maybe Int
forall a. Eq a => a -> [a] -> Maybe Int
`elemIndex` [Char]
"23456789TJQKA")

-- | Map a hand to a representative of its strength for part 2.
-- This version treats @J@ as a wildcard of low individual value.
--
-- >>> strength2 "JKKK2" < strength2 "QQQQ2"
-- True
--
-- >>> sortOn strength2 ["T55J5", "KTJJT", "QQQJA"]
-- ["T55J5","QQQJA","KTJJT"]
strength2 :: String -> [Int]
strength2 :: [Char] -> [Int]
strength2 [Char]
hand =
  [Int] -> Int
forall a. Ord a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
maximum
    [ [Char] -> Int
category ((Char -> Char) -> [Char] -> [Char]
forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
rpl [Char]
hand)
    | Char
alt <- [Char] -> [Char]
forall a. Eq a => [a] -> [a]
nub [Char]
hand
    , let rpl :: Char -> Char
rpl Char
x = if Char
x Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'J' then Char
alt else Char
x
    ] Int -> [Int] -> [Int]
forall a. a -> [a] -> [a]
: (Char -> Int) -> [Char] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map Char -> Int
val [Char]
hand
  where
    val :: Char -> Int
val Char
x = Maybe Int -> Int
forall a. HasCallStack => Maybe a -> a
fromJust (Char
x Char -> [Char] -> Maybe Int
forall a. Eq a => a -> [a] -> Maybe Int
`elemIndex` [Char]
"J23456789TQKA")

-- | Map a hand to an integer representing its set size.
category :: String -> Int
category :: [Char] -> Int
category [Char]
hand =
  case [Int] -> [Int]
forall a. Ord a => [a] -> [a]
sort (Map Char Int -> [Int]
forall a. Map Char a -> [a]
forall (t :: * -> *) a. Foldable t => t a -> [a]
toList ([Char] -> Map Char Int
forall (f :: * -> *) a. (Foldable f, Ord a) => f a -> Map a Int
counts [Char]
hand)) of
    [Int
5]         -> Int
6
    [Int
1,Int
4]       -> Int
5
    [Int
2,Int
3]       -> Int
4
    [Int
1,Int
1,Int
3]     -> Int
3
    [Int
1,Int
2,Int
2]     -> Int
2
    [Int
1,Int
1,Int
1,Int
2]   -> Int
1
    [Int
1,Int
1,Int
1,Int
1,Int
1] -> Int
0
    [Int]
_           -> [Char] -> Int
forall a. HasCallStack => [Char] -> a
error [Char]
"bad hand"