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

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

This problem has us observe multiple rounds of a game where marbles
are drawn from a bag. We have to compute the fewest number of each
color marble that are possible to support each game trace.

>>> :{
:main + "Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green
Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue
Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red
Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red
Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green
"
:}
8
2286

-}
module Main (main) where

import Data.Map (Map)
import Data.Map qualified as Map

import Advent (format)

-- | Parse the input and print the answers to both parts.
--
-- >>> :main
-- 2169
-- 60948
main :: IO ()
IO ()
main =
 do [(Int, [[(Int, String)]])]
input <- [format|2023 2 (Game %d: (%d %s)&(, )&(; )%n)*|]
    let summaries :: [(Int, Map String Int)]
summaries = [(Int
i, [[(Int, String)]] -> Map String Int
summarizeGame [[(Int, String)]]
rounds) | (Int
i, [[(Int, String)]]
rounds) <- [(Int, [[(Int, String)]])]
input]
    Int -> IO ()
forall a. Show a => a -> IO ()
print ([Int] -> Int
forall a. Num a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum [Int
i | (Int
i, Map String Int
summary) <- [(Int, Map String Int)]
summaries, (Int -> Int -> Bool) -> Map String Int -> Map String Int -> Bool
forall k a b.
Ord k =>
(a -> b -> Bool) -> Map k a -> Map k b -> Bool
Map.isSubmapOfBy Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
(<=) Map String Int
summary Map String Int
part1])
    Int -> IO ()
forall a. Show a => a -> IO ()
print ([Int] -> Int
forall a. Num a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum [Map String Int -> Int
forall a. Num a => Map String a -> a
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
product Map String Int
summary | (Int
_, Map String Int
summary) <- [(Int, Map String Int)]
summaries])

-- | Find the minimum marbles needed to play a whole game
summarizeGame :: [[(Int, String)]] -> Map String Int
summarizeGame :: [[(Int, String)]] -> Map String Int
summarizeGame [[(Int, String)]]
rs = (Int -> Int -> Int) -> [Map String Int] -> Map String Int
forall (f :: * -> *) k a.
(Foldable f, Ord k) =>
(a -> a -> a) -> f (Map k a) -> Map k a
Map.unionsWith Int -> Int -> Int
forall a. Ord a => a -> a -> a
max [[(Int, String)] -> Map String Int
summarizeRound [(Int, String)]
r | [(Int, String)]
r <- [[(Int, String)]]
rs]

-- | Find the minimum marbles needed to play round in a game
summarizeRound :: [(Int, String)] -> Map String Int
summarizeRound :: [(Int, String)] -> Map String Int
summarizeRound [(Int, String)]
r = (Int -> Int -> Int) -> [(String, Int)] -> Map String Int
forall k a. Ord k => (a -> a -> a) -> [(k, a)] -> Map k a
Map.fromListWith Int -> Int -> Int
forall a. Num a => a -> a -> a
(+) [(String
color, Int
count) | (Int
count, String
color) <- [(Int, String)]
r]

-- | Marbles available in part 1
part1 :: Map String Int
part1 :: Map String Int
part1 = [(String, Int)] -> Map String Int
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList [(String
"red", Int
12), (String
"green", Int
13), (String
"blue", Int
14)]