I forgot that "weekdays" for a US website means something different for me here in UTC+9.
This was surprisingly fiddly, but I think I managed to do it reasonably neatly.
import Control.Arrow
import Data.Foldable
import Data.List (sortBy)
import Data.List.Split
import Data.Maybe
import Data.Ord
data Fishbone
= Fishbone (Maybe Int) Int (Maybe Int) Fishbone
| Empty
deriving (Eq)
instance Ord Fishbone where
compare = comparing numbers
readInput :: String -> [(Int, Fishbone)]
readInput = map readSword . lines
where
readSword = (read *** build) . break (== ':')
build = foldl' insert Empty . map read . splitOn "," . tail
insert bone x =
case bone of
(Fishbone l c r next)
| isNothing l && x < c -> Fishbone (Just x) c r next
| isNothing r && x > c -> Fishbone l c (Just x) next
| otherwise -> Fishbone l c r $ insert next x
Empty -> Fishbone Nothing x Nothing Empty
spine (Fishbone _ c _ next) = c : spine next
spine Empty = []
numbers :: Fishbone -> [Int]
numbers (Fishbone l c r next) =
(read $ concatMap show $ catMaybes [l, Just c, r])
: numbers next
numbers Empty = []
quality :: Fishbone -> Int
quality = read . concatMap show . spine
part1, part2, part3 :: [(Int, Fishbone)] -> Int
part1 = quality . snd . head
part2 = uncurry (-) . (maximum &&& minimum) . map (quality . snd)
part3 = sum . zipWith (*) [1 ..] . map fst . sortBy (flip compareSwords)
where
compareSwords =
comparing (quality . snd)
<> comparing snd
<> comparing fst
main =
forM_
[ ("everybody_codes_e2025_q05_p1.txt", part1),
("everybody_codes_e2025_q05_p2.txt", part2),
("everybody_codes_e2025_q05_p3.txt", part3)
]
$ \(input, solve) -> readFile input >>= print . solve . readInput