Advent of Code 2024 day 2

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.