2025-12-03 11:18:04 +00:00
|
|
|
module Puzzles.Day3 (puzzle) where
|
|
|
|
|
|
2025-12-03 17:13:47 +00:00
|
|
|
import Control.Monad.Loops (unfoldrM)
|
2025-12-03 14:39:39 +00:00
|
|
|
import Data.Char (digitToInt)
|
|
|
|
|
import Data.Foldable1
|
2025-12-03 17:13:47 +00:00
|
|
|
import Data.List.Extra (dropEnd)
|
2025-12-03 14:39:39 +00:00
|
|
|
import Data.List.NonEmpty (NonEmpty ((:|)), nonEmpty, some1)
|
|
|
|
|
import Data.List.NonEmpty qualified as NE
|
|
|
|
|
import Data.Maybe
|
|
|
|
|
import Data.Text qualified as T
|
|
|
|
|
import Data.Word
|
2025-12-03 11:18:04 +00:00
|
|
|
import Puzzle
|
2025-12-03 14:39:39 +00:00
|
|
|
import Text.Megaparsec
|
|
|
|
|
import Text.Megaparsec.Char (digitChar, newline)
|
2025-12-03 11:18:04 +00:00
|
|
|
|
|
|
|
|
puzzle :: Puzzle
|
|
|
|
|
puzzle =
|
|
|
|
|
Puzzle
|
|
|
|
|
{ number = 3
|
2025-12-03 14:39:39 +00:00
|
|
|
, parser = flip sepEndBy newline $ Bank . fmap (fromIntegral . digitToInt) <$> some1 digitChar
|
2025-12-03 11:18:04 +00:00
|
|
|
, parts =
|
2025-12-03 14:39:39 +00:00
|
|
|
[ T.show
|
|
|
|
|
. sum
|
2025-12-03 17:13:47 +00:00
|
|
|
. map (digitsToInt . fromMaybe (error "battery list too short") . maxBatteries 2)
|
|
|
|
|
, T.show
|
|
|
|
|
. sum
|
|
|
|
|
. map (digitsToInt . fromMaybe (error "battery list too short") . maxBatteries 12)
|
2025-12-03 11:18:04 +00:00
|
|
|
]
|
2025-12-04 20:11:16 +00:00
|
|
|
, extraTests = []
|
2025-12-03 11:18:04 +00:00
|
|
|
}
|
2025-12-03 14:39:39 +00:00
|
|
|
|
|
|
|
|
newtype Bank = Bank (NonEmpty Battery)
|
|
|
|
|
deriving newtype (Eq, Ord, Show)
|
|
|
|
|
|
|
|
|
|
newtype Battery = Battery Word8
|
|
|
|
|
deriving newtype (Eq, Ord, Show, Num, Enum, Real, Integral)
|
|
|
|
|
|
2025-12-03 17:13:47 +00:00
|
|
|
-- maximal n-digit subsequence
|
|
|
|
|
-- returns `Nothing` if list isn't long enough (>= n)
|
|
|
|
|
maxBatteries :: Int -> Bank -> Maybe [Battery]
|
|
|
|
|
maxBatteries n0 (Bank bs0) = flip unfoldrM (n0, NE.toList bs0) \case
|
|
|
|
|
(0, _) -> pure Nothing
|
|
|
|
|
(n, bs) -> do
|
|
|
|
|
(b, i) <- findMax <$> nonEmpty (dropEnd (n - 1) bs)
|
|
|
|
|
pure $ Just (b, (n - 1, drop (i + 1) bs))
|
2025-12-03 14:39:39 +00:00
|
|
|
|
|
|
|
|
-- returns the leftmost element in case of a tie
|
|
|
|
|
findMax :: (Ord a) => NonEmpty a -> (a, Int)
|
|
|
|
|
findMax = foldl1' (\m x -> if fst x > fst m then x else m) . flip NE.zip (0 :| [1 ..])
|
|
|
|
|
|
2025-12-03 17:13:47 +00:00
|
|
|
digitsToInt :: [Battery] -> Int
|
|
|
|
|
digitsToInt = snd . foldr (\b (p, acc) -> (10 * p, acc + fromIntegral b * p)) (1, 0)
|