-
Notifications
You must be signed in to change notification settings - Fork 0
/
Solution.hs
93 lines (76 loc) · 2.71 KB
/
Solution.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
91
92
93
module Day07.Solution
( Rules,
Tree (..),
asPath,
asTree,
countBags,
flattenPaths,
parseRules,
part1,
part2,
pathsToTarget,
)
where
import Advent.Utils (fromRightOrShowError, readInt)
import qualified Data.Map.Strict as Map
import Text.Parsec
part1 :: String -> String
part1 = show . pathsToTarget "shiny gold" . fromRightOrShowError . parseRules
part2 :: String -> String
part2 = show . countBags "shiny gold" . asTree . fromRightOrShowError . parseRules
type Bag = String
type Rules = Map.Map Bag [(Int, Bag)]
newtype Tree a = Tree [(a, Tree a)] deriving (Show, Eq)
parseRules :: String -> Either ParseError Rules
parseRules = parse (Map.fromList <$> try ruleParser `sepEndBy1` newline) ""
ruleParser :: Parsec String () (Bag, [(Int, Bag)])
ruleParser =
((,) <$> (bagParser <* string " bags contain") <*> choice [try containsNoBagsParser, containsBagsParser]) <* char '.'
containsNoBagsParser :: Parsec String () [(Int, Bag)]
containsNoBagsParser = do
_ <- string " no other bags"
pure []
containsBagsParser :: Parsec String () [(Int, Bag)]
containsBagsParser = bagCountParser `sepBy1` char ','
bagCountParser :: Parsec String () (Int, Bag)
bagCountParser = (,) <$> countParser <*> bagParser'
where
countParser = space *> (readInt <$> many digit) <* space
bagParser' = bagParser <* space <* skipMany1 letter
bagParser :: Parsec String () Bag
bagParser =
manyTill anyChar $
try $
lookAhead $
string " bag"
pathsToTarget :: Bag -> Rules -> Int
pathsToTarget target = Map.size . Map.filter containsTarget . flattenPaths
where
containsTarget :: [[(Int, Bag)]] -> Bool
containsTarget = any (any (\(_, bag) -> bag == target))
flattenPaths :: Rules -> Map.Map Bag [[(Int, Bag)]]
flattenPaths = Map.map asPath . asTree
asTree :: Rules -> Map.Map Bag (Tree (Int, Bag))
asTree rules = Map.mapWithKey (\key _ -> fn key (Tree [])) rules
where
fn :: Bag -> Tree (Int, Bag) -> Tree (Int, Bag)
fn key history = Tree $ map (\kid@(_, nextKey) -> (kid, fn nextKey history)) kids
where
kids = rules Map.! key
asPath :: Tree a -> [[a]]
asPath (Tree nodes) = concatMap walkNode nodes
where
walkNode :: (a, Tree a) -> [[a]]
walkNode (a, tree) = go [a] tree
go :: [a] -> Tree a -> [[a]]
go history (Tree []) = [history]
go history (Tree nodes') = concatMap (\(a, tree) -> go (a : history) tree) nodes'
countBags :: Bag -> Map.Map Bag (Tree (Int, Bag)) -> Int
countBags target = go . (Map.! target)
where
go :: Tree (Int, Bag) -> Int
go (Tree []) = 1
go (Tree nodes) = sum $ map (uncurry go') nodes
go' :: (Int, b) -> Tree (Int, Bag) -> Int
go' node (Tree []) = fst node
go' node tree = fst node + fst node * go tree