After the struggle of day 24, the final day 25 puzzle was a gentle relief.
A Schematic
is either a Lock
or a Key
. Both have a list of heights.
data Schematic = Lock [Int] | Key [Int]
deriving (Show, Eq, Ord)
I read the input by splitting it into lines
, splitting it into schematics on blank lines, making the schematics, then splitting into locks and keys.
makeSchematics :: String -> ([Schematic], [Schematic])
makeSchematics = partition isLock . fmap makeSchematic . splitOn [""] . lines
where isLock (Lock _) = True
isLock _ = False
makeSchematic :: [String] -> Schematic
makeSchematic ss
| isKeySchematic ss = Key $ heightsOf ss
| isLockSchematic ss = Lock $ heightsOf ss
| otherwise = error "Invalid schematic"
isKeySchematic, isLockSchematic :: [String] -> Bool
isKeySchematic [a,_,_,_,_,_,b] = a == "....." && b == "#####"
isLockSchematic [a,_,_,_,_,_,b] = a == "#####" && b == "....."
heightsOf :: [String] -> [Int]
heightsOf = fmap ((-1 +) . length . filter (== '#')) . transpose
That converts the input into the list-of-heights form given in the puzzle description.
A lock and key are compatible
if all the matching heights sum to less than or equal to five. A list comprehension finds all possible pairs of lock and key, filtered by compatibility.
part1 :: [Schematic] -> [Schematic] -> Int
part1 locks keys = length [(l, k) | l <- locks, k <- keys, compatible l k]
compatible :: Schematic -> Schematic -> Bool
compatible (Lock ls) (Key ks) = all (<= 5) $ zipWith (+) ls ks
And that's the end of the Advent of Code for another year!

Code
You can get the code from my locally-hosted Git repo, or from Codeberg.