Today’s problem is… password validation? As you can guess from the title, the vast majority of the work was just parsing the puzzle input. The puzzle input looks like the following:
1-3 a: abcde
1-3 b: cdefg
2-9 c: ccccccccc
which is meant to be interpreted as passwords together with whatever policy requirements each password needed to meet. We want to parse out each line into the following data type:
data PasswordEntry = PasswordEntry
_policy :: ((Int, Int), Char),
{ _password :: String
}
It’d be pretty reasonable to reach for a regex here, although in Haskell it might be more idiomatic to use a parser combinator library like Megaparsec (and that’s what I’d probably do in production). For a toy case like this, though, if you’re willing to swallow writing a partial function, the nested pattern-matching approach is actually quite nice:
parsePasswordEntry :: Text -> PasswordEntry
=
parsePasswordEntry line let [rawRange, [char, ':'], password] = words $ Text.unpack line
'-' : rawMax) = break (== '-') rawRange
(rawMin, in PasswordEntry ((read rawMin, read rawMax), char) password
This is a nice reminder that Haskell makes for a pretty pleasant slap-something-together language even when you don’t care about production-readiness, thanks to the expressiveness of pattern matching. Haskell gets (rightfully) bashed for having its default String type be just a type synonym for [Char]
, but we benefit from it here by getting to pattern match on the resulting lists like this.
The actual password validation bits are quite simple. For part 1, we interpret the policy as giving us a character whose number of occurrences in the password needs to fall within a particular range:
isValid1 :: PasswordEntry -> Bool
PasswordEntry ((lo, hi), char) password) =
isValid1 (<= count && count <= hi
lo where
= length $ filter (== char) password
count
answer1 :: [PasswordEntry] -> Int
= length $ filter isValid1 inp answer1 inp
Don’t have much to add here. Partially-applied operators like in (== char)
are nice for readability. For part 2, we interpret the policy’s numbers as (one-indexed) indices, where the provided character must appear at exactly one of the two indices in the password:
isValid2 :: PasswordEntry -> Bool
PasswordEntry ((lo, hi), char) password) =
isValid2 (/= checkInd hi
checkInd lo where
= password ^? ix (ind - 1) == Just char
checkInd ind
answer2 :: [PasswordEntry] -> Int
= length $ filter isValid2 inp answer2 inp
(/=)
on booleans is xor :)- The
^? ix
bit is there because I habitually importLens.Micro.Platform
for convenience, but you could do just as well with any other safe index (or even the unsafe!!
).
As always, my full solution is on GitHub: https://github.com/ewilden/aoc-2020/blob/main/src/Day02.hs