Another year, another Advent of Code! This day 1 was surprisingly tricky, due to a very uncharacteristic oversight by the Advent of Code team.
Part 1 was straightforward: filter each line to find the digits, combine the first and last digits into a new string, read that string.
part1 :: [String] -> Int
part1 = sum . (fmap getCalibration)
getCalibration :: String -> Int
getCalibration calibration = read [head digits, last digits]
where digits = filter isDigit calibration
Part 2 was where things got complicated, mainly due to some imprecision in the problem specifications. There is an edge case that isn't revealed in the given examples, that caught out me (and many others it seems).
The input string oneight
has one
and eight
as overlapping words. It turns out, that central e
should be included in both the words, so the found numbers should be 1
and 8
.
I ended up writing the conversion as a fold
over the tails
of the input string, testing each prefix of each tail of the string for being a digit or a number word. If it was one of those, I add the corresponding digit to the fold's result; if not, it's skipped.
part2 :: [String] -> Int
part2 calibrations = sum $ fmap (getCalibration . replaceNums) calibrations
replaceNums :: String -> String
replaceNums haystack = reverse $ foldl' go "" $ tails haystack
where go acc [] = acc
go acc xs
| "one" `isPrefixOf` xs = '1' : acc
| "two" `isPrefixOf` xs = '2' : acc
| "three" `isPrefixOf` xs = '3' : acc
| "four" `isPrefixOf` xs = '4' : acc
| "five" `isPrefixOf` xs = '5' : acc
| "six" `isPrefixOf` xs = '6' : acc
| "seven" `isPrefixOf` xs = '7' : acc
| "eight" `isPrefixOf` xs = '8' : acc
| "nine" `isPrefixOf` xs = '9' : acc
| isDigit (head xs) = (head xs) : acc
| otherwise = acc
I did consider using something like a Map
to store the data, but in the end thought it was just as simple to keep it explicit in the function.
Code
You can get the code from my locally-hosted Git repo, or from Gitlab.