#!/usr/bin/env nix-shell
#!nix-shell --pure -i "runghc -- -i../" -p "haskellPackages.ghcWithPackages (pkgs: with pkgs; [ ])"
import Aoc (readAndParseStdin)
import Data.Bifunctor (Bifunctor (first))
import Data.Map (Map, adjust, elems, (!))
import qualified Data.Map as Map
import Data.Set (Set, intersection)
import qualified Data.Set as Set
import Text.Parsec (between, char, choice, digit, endBy, endBy1, getInput, letter, many1, optionMaybe, sepBy, sepBy1, string, try)
import Text.Parsec.Char (noneOf)
import Text.Parsec.String (Parser)
main = do
input <- readAndParseStdin parse
print $ part1 input
print $ part2 input
part1 :: Game -> Int
part1 input = sum $ map (sum . elems) $ filter (evaluateInput "in" (workflows input)) (inputs input)
part2 :: Game -> Int
part2 input = sum $ map (product . fst) $ filter ((== Accept) . snd) $ map (first $ elems . Map.map Set.size) $ calculateAllowedBounds $ findAllCompletionBounds "in" $ workflows input
calculateAllowedBounds :: [([Cond], Action)] -> [(Map ItemKey (Set Int), Action)]
calculateAllowedBounds =
let rangeSet = Set.fromList [1 .. 4000]
initAcc = Map.fromList [(X, rangeSet), (M, rangeSet), (A, rangeSet), (S, rangeSet)]
in map (first $ calcSet initAcc)
where
buildRange :: Cond -> Set Int
buildRange cond = case cond of
Cond (_, Lt, v) -> Set.fromList [1 .. v - 1]
Cond (_, Gt, v) -> Set.fromList [v + 1 .. 4000]
calcSet :: Map ItemKey (Set Int) -> [Cond] -> Map ItemKey (Set Int)
calcSet acc [] = acc
calcSet acc (x : xs) =
let Cond (k, _, _) = x
in calcSet (adjust (`intersection` buildRange x) k acc) xs
findAllCompletionBounds :: String -> Map String [(Maybe Cond, Action)] -> [([Cond], Action)]
findAllCompletionBounds entryPoint workflows = recurse [] (workflows ! entryPoint)
where
recurse :: [Cond] -> [(Maybe Cond, Action)] -> [([Cond], Action)]
recurse cacc [] = []
recurse cacc ((cond, action) : xs) =
let nextCacc = case cond of
Just c -> c : cacc
Nothing -> cacc
inverseCacc = case cond of
Just c -> invertCond c : cacc
Nothing -> cacc
in case action of
Reject -> (nextCacc, Reject) : recurse inverseCacc xs
Accept -> (nextCacc, Accept) : recurse inverseCacc xs
Redirect s -> recurse nextCacc (workflows ! s) ++ recurse inverseCacc xs
where
invertCond (Cond (k, Lt, v)) = Cond (k, Gt, v - 1)
invertCond (Cond (k, Gt, v)) = Cond (k, Lt, v + 1)
evaluateInput :: String -> Map String [(Maybe Cond, Action)] -> Map ItemKey Int -> Bool
evaluateInput entryPoint workflows input = go entryPoint
where
go workflow = case evaluateSingleWorkflow (workflows ! workflow) input of
Accept -> True
Reject -> False
Redirect s -> go s
evaluateSingleWorkflow workflow input = go workflow
where
go [] = error "no fallback"
go ((cond, action) : xs) = case cond of
Nothing -> action
Just (Cond (k, Lt, v)) -> if (input ! k) < v then action else go xs
Just (Cond (k, Gt, v)) -> if (input ! k) > v then action else go xs
parse :: Parser Game
parse = do
workflows <- Map.fromList <$> parseWorkflow `endBy1` char '\n'
_ <- char '\n'
inputs <- (Map.fromList <$> parseInput) `sepBy` char '\n'
return
Game
{ workflows,
inputs
}
parseInput :: Parser [(ItemKey, Int)]
parseInput = braced $ kvPair `sepBy` char ','
where
kvPair = do
key <- parseItemKey <* char '='
value <- read <$> many1 digit
return (key, value)
braced = between (char '{') (char '}')
parseWorkflow :: Parser (String, [(Maybe Cond, Action)])
parseWorkflow = do
key <- many1 (noneOf "{\n")
conds <- braced $ parseWorkflowItem `sepBy` char ','
return (key, conds)
parseWorkflowItem :: Parser (Maybe Cond, Action)
parseWorkflowItem = do
cond <- optionMaybe (try parseCond <* char ':')
action <-
choice
[ Accept <$ char 'A',
Reject <$ char 'R',
Redirect <$> many1 letter
]
return (cond, action)
parseCond :: Parser Cond
parseCond = do
item <- parseItemKey
cond <-
choice
[ Lt <$ char '<',
Gt <$ char '>'
]
val <- read <$> many1 digit
return $ Cond (item, cond, val)
parseItemKey :: Parser ItemKey
parseItemKey =
choice
[ X <$ char 'x',
M <$ char 'm',
A <$ char 'a',
S <$ char 's'
]
data Game = Game
{ workflows :: Map String [(Maybe Cond, Action)],
inputs :: [Map ItemKey Int]
}
deriving (Show)
newtype Cond = Cond (ItemKey, CondOp, Int) deriving (Show)
data CondOp = Lt | Gt deriving (Enum, Show)
data Action = Accept | Reject | Redirect String deriving (Show, Eq)
data ItemKey = X | M | A | S deriving (Enum, Show, Ord, Eq)