From 32c934335cc4db05f4e9a15a7ff4b295627338bf Mon Sep 17 00:00:00 2001 From: Julien Dessaux Date: Tue, 9 May 2023 23:53:22 +0200 Subject: 2020-21 part 1 in haskell --- 2020/21-Allergen_Assessment/first.hs | 66 ++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 2020/21-Allergen_Assessment/first.hs (limited to '2020/21-Allergen_Assessment/first.hs') diff --git a/2020/21-Allergen_Assessment/first.hs b/2020/21-Allergen_Assessment/first.hs new file mode 100644 index 0000000..9891a9e --- /dev/null +++ b/2020/21-Allergen_Assessment/first.hs @@ -0,0 +1,66 @@ +-- requires cabal install --lib megaparsec parser-combinators +module Main (main) where +import Control.Monad (void, when) +import Data.List qualified as L +import Data.Map qualified as M +import Data.Maybe (catMaybes) +import Data.Set qualified as S +import Data.Void (Void) +import Text.Megaparsec +import Text.Megaparsec.Char +import System.Exit (die) + +exampleExpectedOutput = 5 + +newtype Ingredient = Ingredient String deriving (Eq, Ord, Read, Show) +newtype Allergen = Allergen String deriving (Eq, Ord, Read, Show) +type Rule = ([Ingredient], [Allergen]) +type Input = [Rule] + +type Parser = Parsec Void String + +parseRule :: Parser Rule +parseRule = do + ingredients <- some ((Ingredient <$> some letterChar) <* char ' ') + void $ string "(contains " + allergens <- some ((Allergen <$> some letterChar) <* optional (string ", ")) + void $ string ")\n" + return $ (ingredients, allergens) + +parseInput' :: Parser Input +parseInput' = some parseRule <* eof + +parseInput :: String -> IO Input +parseInput filename = do + input <- readFile filename + case runParser parseInput' filename input of + Left bundle -> die $ errorBundlePretty bundle + Right input' -> return input' + +compute :: Input -> Int +compute rules = sum . map count $ filter isUnused ingredients + where + ingredients :: [Ingredient] + ingredients = S.elems . S.fromList . concat $ map fst rules + candidates :: [[Ingredient]] + candidates = M.elems candidatesMap + candidatesMap :: M.Map Allergen [Ingredient] + candidatesMap = L.foldl' appendIngredientsForRule M.empty rules + appendIngredientsForRule :: M.Map Allergen [Ingredient] -> Rule -> M.Map Allergen [Ingredient] + appendIngredientsForRule m (is, as) = L.foldl' (appendIngredientsForAllergen is) m as + appendIngredientsForAllergen :: [Ingredient] -> M.Map Allergen [Ingredient] -> Allergen -> M.Map Allergen [Ingredient] + appendIngredientsForAllergen is m a = case M.lookup a m of + Nothing -> M.insert a is m + Just is' -> M.insert a (L.intersect is is') m + isUnused :: Ingredient -> Bool + isUnused i = (catMaybes $ map (L.elemIndex i) candidates) == [] + count :: Ingredient -> Int + count i = sum $ map (length . L.elemIndices i) (map fst rules) + +main :: IO () +main = do + example <- parseInput "example" + let exampleOutput = compute example + when (exampleOutput /= exampleExpectedOutput) (die $ "example failed: got " ++ show exampleOutput ++ " instead of " ++ show exampleExpectedOutput) + input <- parseInput "input" + print $ compute input -- cgit v1.2.3