Day 6 was another input file of groups separated by blank lines. As I mentioned on day 4, this isn't what Megaparsec is good for, so time to try out Attoparsec.
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 S.size
. That's map
pped 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
Code
You can find the code here or on Gitlab.