Day 2 was another problem that suited Haskell. Most of the problem was tackled by my writing down the problem definition as a series of predicate functions.
As with day 1, I read the input using a combination of lines
, words
, and read
.
Definitions
A report is safe if the differences between adjacent terms are
- all the same sign
- all big enough
- all small enough
isSafe, allSameSign, bigEnough, smallEnough, safeWhenDamped :: [Int] -> Bool
isSafe xs = allSameSign diffs && bigEnough diffs && smallEnough diffs
where diffs = zipWith (-) xs (tail xs)
Each of those tests has its own predicate.
allSameSign xs = (all (>0) xs) || (all (<0) xs)
bigEnough = all ((>= 1) . abs)
smallEnough = all ((<= 3) . abs)
From that, I count how many safe reports there are with length
and filter
.
part1 reports = length $ filter isSafe reports
Damping
For part 2, I need to check the "damped" reports. The damped reports are one with one element missing.
inits
and tails
split a list into all prefixes and suffixes; I combine them with (++)
after I drop 1
element from the suffixes.
damped :: [Int] -> [[Int]]
damped line = zipWith (++) (inits line) (drop 1 $ tails line)
A report is "safe when damped" if any of the damped reports is safe.
safeWhenDamped = (any isSafe) . damped
That gives the modified version of isSafe
part2 reports = length $ filter isSafe' reports
where isSafe' l = isSafe l || safeWhenDamped l
Addendum
Having that big conjunction of predicates in isSafe
annoyed me slightly. Surely there's a better way to combine a set of predicates? If I put the predicates in a list, I can take advantage of a list being an Applicative Functor
and use <*>
to apply each function to the differences.
isSafe xs = and $ [allSameSign, bigEnough, smallEnough] <*> (pure diffs)
where diffs = zipWith (-) xs (tail xs)
Possibly more elegant, but I doubt it's any easier to read.
Code
You can get the code from my locally-hosted Git repo, or from Codeberg.