Without space consumers to worry about, and therefore more control over how and when whitespace is consumed, the parser ended up much neater than the Megaparsec version would have been. Conversely, it's a quite fragile parser: changes in the positioning of whitespace would cause the parsing to fail.
blankLines = skipMany1 endOfLine personP = S.fromList <$> many1 letter groupP = sepBy1 personP endOfLine groupsP = sepBy groupP blankLines
I think that's quite readable: a person is a set of letters. A group is a collection of people, separated by single newlines. Groups are separated by at least one blank line.
Given a list of list of sets of questions, we're back to territory where the code is much shorter than the explanation. I can find all the questions answered by a group by finding the
S.unions of the group; I count them with
mappped over all the groups and the counts summed.
part1 groups = sum $ map (S.size . S.unions) groups
Part 2 is the same, only there isn't an
S.intersections function already in the library (what would the base case be, for a general
Foldable?). So I had to build one.
part2 groups = sum $ map (S.size . intersections) groups intersections :: Ord a => [S.Set a] -> S.Set a intersections group = foldl S.intersection (head group) group