Our first goal here is to simplify the input. We conjure up the `replace`

function and make some replacements to our
text. We also define a data type for bags holding other bags and how to parse the cleaned up text.

```
data Relation = Relation String (HashSet (Int, String))
deriving (Show, Eq)
replace :: Eq a => [a] -> [a] -> [a] -> [a]
replace n r h = intercalate r (splitOn n h)
makeRelation :: [String] -> Relation
makeRelation (x:xs) = Relation x (relations xs)
where relations (a:b:rest) = singleton (read a, b) <> relations rest
relations [] = mempty
relations _ = error "impossibru"
makeRelation _ = error "impossible"
relations = fmap makeRelation . cleanup' . cleanup $ input
cleanup = replace "," "" . replace "." "" . replace "bag" "" . replace "bags" "bag" . replace "contain" ""
cleanup' = fmap (fmap concat . split (whenElt (all isDigit)) . words) . lines
```

Here `whenElt`

is a exceedingly poorly named function (in my opinion) which will split when the condition is met.
Therefore `cleanup'`

will split the input into lines, and then split each line into words and group them into ```
[color,
count, color, count, color, ...]
```

.

Part A wants us to traverse the relation backwards, so we just make a forwards tree and BFS forwards instead.

```
type UpGraph = HashMap String (HashSet String)
(<>?) :: Monoid a => ASetter' s (Maybe a) -> a -> s -> s
l <>? a = over l (fmap (<>a) . maybe (Just mempty) Just)
buildContainer :: UpGraph -> Relation -> UpGraph
buildContainer g (Relation up rs) = foldl' (\g' r -> g' & at (snd r) <>? singleton up) g rs
bfsA :: String -> HashSet String -> UpGraph -> HashSet String
bfsA start visited g =
if start `member` visited then mempty
else
case g ^? ix start of
Just ups -> ups <> foldl' (<>) mempty (HS.map (\u -> bfsA u visited' g) ups)
Nothing -> singleton start
where visited' = visited <> singleton start
container = foldl' buildContainer mempty $ relations
```

Here I define this nifty little combinator combining both `?~`

and `<>~`

which will let me use `at`

and transform
`Nothing`

into `mempty`

. This lets us avoid the case where the key does not yet exist. The BFS is fairly straightfoward.
It is important to remember that you also need to count the bags along the way instead of only the bags at the end. The
answer is then the length of the resulting set `length $ bfsA "shinygold" mempty container`

.

Part B is a bit more straightforward. We again define a different type of tree and perform a simple BFS.

```
type DownGraph = HashMap String (HashSet (Int, String))
buildContained :: DownGraph -> Relation -> DownGraph
buildContained g (Relation up rs) = g & at up ?~ rs
bfsB :: String -> HashSet String -> DownGraph -> Int
bfsB start visited g =
if start `member` visited then 0
else
case g ^? ix start of
Just rs -> 1 + sum ((\(n, r) -> n * bfsB r visited' g) <$> toList rs)
Nothing -> 1
where visited' = visited <> singleton start
contained = foldl' buildContained mempty $ relations
```

Here the BFS returns the number of sub bags including itself. Therefore we must be careful and subtract one to get our
final answer of `bfsB "shinygold" mempty contained - 1`

. We can, however, also write the BFS as

```
case g ^? ix start of
Just rs -> sum ((\(n, r) -> n + n * bfsB r visited' g) <$> toList rs)
Nothing -> 0
```

which will directly the sub bags at the cost of a few more additions if subtracting one is not groovy enough for you.