-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDay21.hs
90 lines (75 loc) · 2.8 KB
/
Day21.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
module Day21
( part1
, part2
) where
import Control.Monad (void)
import Data.Bifunctor (second)
import Data.Either (fromRight)
import Data.List as L (filter, map)
import Data.Set as St (Set, difference, empty, filter,
fromList, intersection, map,
member, notMember, null, size,
toList, union, unions)
import Helpers.Parsers (Parser)
import Text.Megaparsec (eof, many, manyTill, optional, parse,
sepBy, try, (<|>))
import Text.Megaparsec.Char (char, eol, letterChar, string)
import Debug.Trace
type Ingredient = String
type Allergen = String
type Food = (Set Ingredient, Set Allergen)
parseList :: Parser [Food]
parseList = many parseFood <* eof
parseFood :: Parser Food
parseFood = do
ingredients <- parseIngredients
string "contains "
allergens <- parseAllergens
return (ingredients, allergens)
parseIngredients :: Parser (Set Ingredient)
parseIngredients =
fromList <$> manyTill (manyTill letterChar (char ' ')) (char '(')
parseAllergens :: Parser (Set Allergen)
parseAllergens = fromList <$> manyTill parseAllergen eol
parseAllergen :: Parser Allergen
parseAllergen = do
allergen <- many letterChar
try (void . string $ ", " :: Parser ()) <|> (void . string $ ")" :: Parser ())
return allergen
refinedSets :: [Food] -> Set (Allergen, Set Ingredient)
refinedSets foods =
St.map
(\a ->
( a
, foldl1 intersection . L.map fst . L.filter (\(_, b) -> a `member` b) $
foods)) .
unions . L.map snd $
foods
noAllergen :: [Food] -> Int
noAllergen foods =
sum .
L.map (\i -> length . L.filter (\f -> i `member` fst f) $ foods) .
toList . difference allFoods $
unsure
where
refined = refinedSets foods
unsure = unions . St.map snd $ refined
allFoods = unions . L.map fst $ foods
pairAllergens :: [Food] -> [Ingredient]
pairAllergens foods = L.map snd . toList . simplify $ (empty, refined)
where
refined = refinedSets foods
simplify (paired, unpaired)
| St.null unpaired = paired
| otherwise = simplify (paired `union` newPaired, remaining)
where
newPaired =
St.map (second (head . toList)) . St.filter ((== 1) . size . snd) $
unpaired
pruned = St.filter (\x -> fst x `notElem` St.map fst newPaired) unpaired
remaining =
St.map (second (St.filter (`notElem` St.map snd newPaired))) pruned
part1 :: Bool -> String -> String
part1 _ = show . noAllergen . fromRight [] . parse parseList ""
part2 :: Bool -> String -> String
part2 _ = show . pairAllergens . fromRight [] . parse parseList ""