2025-12-08 12:48:49 +00:00
|
|
|
{-# LANGUAGE PackageImports #-}
|
2025-12-31 01:18:05 +00:00
|
|
|
{-# LANGUAGE TypeFamilies #-}
|
2025-12-08 12:48:49 +00:00
|
|
|
|
|
|
|
|
module Pre (
|
|
|
|
|
module BasePrelude,
|
|
|
|
|
module Control.Applicative,
|
|
|
|
|
module Control.Monad,
|
|
|
|
|
module Control.Monad.Loops,
|
|
|
|
|
module Control.Monad.State,
|
|
|
|
|
module Data.Bifunctor,
|
|
|
|
|
module Data.Bool,
|
|
|
|
|
module Data.Char,
|
|
|
|
|
module Data.Foldable,
|
|
|
|
|
module Data.Foldable1,
|
2025-12-08 22:48:29 +00:00
|
|
|
module Data.Function,
|
2025-12-08 12:48:49 +00:00
|
|
|
module Data.Functor,
|
|
|
|
|
module Data.List,
|
|
|
|
|
module Data.List.Extra,
|
|
|
|
|
module Data.List.NonEmpty,
|
|
|
|
|
module Data.Maybe,
|
|
|
|
|
module Data.Ord,
|
|
|
|
|
module Data.Sequence,
|
|
|
|
|
module Data.Stream.Infinite,
|
|
|
|
|
module Data.Text,
|
|
|
|
|
module Data.Text.Encoding,
|
2025-12-08 16:34:30 +00:00
|
|
|
module Data.Traversable,
|
2025-12-09 13:37:39 +00:00
|
|
|
module Data.Tuple.Extra,
|
2025-12-08 12:48:49 +00:00
|
|
|
module Data.Void,
|
|
|
|
|
module Data.Word,
|
|
|
|
|
module Linear,
|
2025-12-08 22:01:49 +00:00
|
|
|
module Safe,
|
2025-12-08 12:48:49 +00:00
|
|
|
module Text.Megaparsec,
|
|
|
|
|
module Text.Megaparsec.Char,
|
|
|
|
|
module Text.Megaparsec.Char.Lexer,
|
|
|
|
|
Puzzle (..),
|
2025-12-08 23:38:48 +00:00
|
|
|
digit,
|
|
|
|
|
digitsToInt,
|
2025-12-09 00:02:54 +00:00
|
|
|
listIndex,
|
2025-12-09 10:47:32 +00:00
|
|
|
allUnorderedPairs,
|
2025-12-09 13:37:39 +00:00
|
|
|
adjacentPairs,
|
|
|
|
|
sortPair,
|
2026-01-05 14:37:25 +00:00
|
|
|
HList (..),
|
|
|
|
|
hlistLength,
|
2026-01-04 02:14:35 +00:00
|
|
|
HListF (..),
|
2026-01-04 02:39:33 +00:00
|
|
|
foldHListF,
|
2026-01-04 02:57:38 +00:00
|
|
|
foldHListF0,
|
2026-01-04 02:14:35 +00:00
|
|
|
mapHListF,
|
2026-01-05 14:37:25 +00:00
|
|
|
hlistfLength,
|
2025-12-16 16:15:11 +00:00
|
|
|
(/\),
|
2025-12-31 01:18:05 +00:00
|
|
|
(/\\),
|
2025-12-16 16:15:11 +00:00
|
|
|
nil,
|
2026-01-04 02:14:35 +00:00
|
|
|
Fanout (..),
|
2026-01-05 14:37:25 +00:00
|
|
|
TestTree (..),
|
|
|
|
|
TestName,
|
|
|
|
|
mkTestName,
|
|
|
|
|
getTestTree,
|
|
|
|
|
runTests,
|
|
|
|
|
assertEqual,
|
|
|
|
|
assert,
|
|
|
|
|
golden,
|
2025-12-08 12:48:49 +00:00
|
|
|
)
|
|
|
|
|
where
|
|
|
|
|
|
|
|
|
|
import "base" Prelude as BasePrelude hiding (
|
|
|
|
|
foldl1,
|
|
|
|
|
foldr1,
|
|
|
|
|
head,
|
|
|
|
|
init,
|
|
|
|
|
last,
|
|
|
|
|
maximum,
|
|
|
|
|
minimum,
|
|
|
|
|
tail,
|
|
|
|
|
unzip,
|
2025-12-09 00:02:54 +00:00
|
|
|
(!!),
|
2025-12-08 12:48:49 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
import Control.Applicative
|
2026-01-05 14:37:25 +00:00
|
|
|
import Control.Exception (SomeException)
|
2025-12-08 12:48:49 +00:00
|
|
|
import Control.Monad
|
2026-01-05 14:37:25 +00:00
|
|
|
import Control.Monad.Catch (MonadCatch, try)
|
2025-12-08 12:48:49 +00:00
|
|
|
import Control.Monad.Loops
|
|
|
|
|
import Control.Monad.State
|
|
|
|
|
import Data.Bifunctor
|
|
|
|
|
import Data.Bool
|
|
|
|
|
import Data.Char
|
|
|
|
|
import Data.Foldable hiding (foldl1, foldr1, maximum, maximumBy, minimum, minimumBy)
|
|
|
|
|
import Data.Foldable1
|
2025-12-08 22:48:29 +00:00
|
|
|
import Data.Function
|
2025-12-08 12:48:49 +00:00
|
|
|
import Data.Functor
|
2026-01-04 02:14:35 +00:00
|
|
|
import Data.Functor.Contravariant
|
2025-12-31 01:18:05 +00:00
|
|
|
import Data.Kind (Type)
|
2025-12-16 16:15:11 +00:00
|
|
|
import Data.List (List, sortOn, transpose)
|
2025-12-11 11:34:36 +00:00
|
|
|
import Data.List.Extra (dropEnd, enumerate, firstJust, notNull, splitOn)
|
2025-12-09 00:26:22 +00:00
|
|
|
import Data.List.NonEmpty (NonEmpty ((:|)), nonEmpty, some1, tail, tails)
|
2025-12-08 12:48:49 +00:00
|
|
|
import Data.Maybe
|
|
|
|
|
import Data.Ord
|
|
|
|
|
import Data.Sequence (Seq)
|
|
|
|
|
import Data.Stream.Infinite (Stream ((:>)))
|
2026-01-05 14:37:25 +00:00
|
|
|
import Data.String (IsString)
|
2025-12-08 12:48:49 +00:00
|
|
|
import Data.Text (Text)
|
2025-12-31 01:18:05 +00:00
|
|
|
import Data.Text qualified as T
|
2025-12-08 12:48:49 +00:00
|
|
|
import Data.Text.Encoding (encodeUtf8)
|
2026-01-05 14:37:25 +00:00
|
|
|
import Data.Text.IO qualified as T
|
2025-12-08 16:34:30 +00:00
|
|
|
import Data.Traversable
|
2026-01-05 14:37:25 +00:00
|
|
|
import Data.Tree
|
2025-12-09 13:37:39 +00:00
|
|
|
import Data.Tuple.Extra ((&&&))
|
2025-12-08 12:48:49 +00:00
|
|
|
import Data.Void
|
|
|
|
|
import Data.Word
|
|
|
|
|
import Linear (V2 (..))
|
2025-12-08 22:01:49 +00:00
|
|
|
import Safe
|
2025-12-08 12:48:49 +00:00
|
|
|
import Text.Megaparsec hiding (Pos, State, Stream, many, some)
|
|
|
|
|
import Text.Megaparsec.Char
|
|
|
|
|
import Text.Megaparsec.Char.Lexer (decimal)
|
|
|
|
|
|
2025-12-16 16:15:11 +00:00
|
|
|
data Puzzle = forall input outputs. Puzzle
|
2025-12-08 12:48:49 +00:00
|
|
|
{ number :: Word
|
2025-12-08 22:42:29 +00:00
|
|
|
, parser :: Bool -> Parsec Void Text input
|
2025-12-31 01:18:05 +00:00
|
|
|
, parts :: PuzzleParts input outputs
|
2026-01-05 14:37:25 +00:00
|
|
|
, extraTests :: Bool -> FilePath -> [TestTree IO (input, HList outputs)]
|
2025-12-08 12:48:49 +00:00
|
|
|
}
|
2025-12-08 23:38:48 +00:00
|
|
|
|
|
|
|
|
digit :: (Token s ~ Char, Num b, MonadParsec e s f) => f b
|
|
|
|
|
digit = fromIntegral . digitToInt <$> digitChar
|
|
|
|
|
|
|
|
|
|
digitsToInt :: (Integral a) => [a] -> Int
|
2025-12-13 11:24:46 +00:00
|
|
|
digitsToInt = foldl' (\acc d -> acc * 10 + fromIntegral d) 0
|
2025-12-09 00:02:54 +00:00
|
|
|
|
|
|
|
|
listIndex :: Int -> [a] -> Maybe a
|
|
|
|
|
listIndex n =
|
|
|
|
|
if n < 0
|
|
|
|
|
then const Nothing
|
|
|
|
|
else \case
|
|
|
|
|
[] -> Nothing
|
|
|
|
|
x : xs -> if n == 0 then Just x else listIndex (n - 1) xs
|
2025-12-09 10:47:32 +00:00
|
|
|
|
2025-12-09 11:00:01 +00:00
|
|
|
allUnorderedPairs :: Bool -> [a] -> [(a, a)]
|
|
|
|
|
allUnorderedPairs diagonals = concat . join (zipWith (flip $ map . (,)) . (bool tail toList diagonals) . tails)
|
2025-12-09 13:37:39 +00:00
|
|
|
|
|
|
|
|
adjacentPairs :: [b] -> [(b, b)]
|
|
|
|
|
adjacentPairs = \case
|
|
|
|
|
[] -> []
|
|
|
|
|
x : xs -> zip (x : xs) xs
|
|
|
|
|
|
|
|
|
|
sortPair :: (Ord a) => (a, a) -> (a, a)
|
|
|
|
|
sortPair (a, b) = if a <= b then (a, b) else (b, a)
|
2025-12-09 16:47:12 +00:00
|
|
|
|
2026-01-04 02:14:35 +00:00
|
|
|
type PuzzleParts input = HListF (Fanout ((->) input) (Op Text))
|
2025-12-31 01:18:05 +00:00
|
|
|
infixr 9 /\\
|
|
|
|
|
(/\\) :: (input -> output, output -> Text) -> PuzzleParts input outputs -> PuzzleParts input (output : outputs)
|
2026-01-04 02:14:35 +00:00
|
|
|
(/\\) (f, o) = HConsF $ Fanout (f, Op o)
|
2025-12-16 16:15:11 +00:00
|
|
|
infixr 9 /\
|
2025-12-31 01:18:05 +00:00
|
|
|
(/\) :: (Show output) => (input -> output) -> PuzzleParts input outputs -> PuzzleParts input (output : outputs)
|
2026-01-04 02:14:35 +00:00
|
|
|
(/\) f = HConsF $ Fanout (f, Op T.show)
|
2025-12-31 01:18:05 +00:00
|
|
|
nil :: PuzzleParts input '[]
|
2026-01-04 02:14:35 +00:00
|
|
|
nil = HNilF
|
|
|
|
|
|
2026-01-05 14:37:25 +00:00
|
|
|
data HList (as :: List Type) :: Type where
|
|
|
|
|
HNil :: HList '[]
|
|
|
|
|
HCons ::
|
|
|
|
|
a ->
|
|
|
|
|
HList as ->
|
|
|
|
|
HList (a ': as)
|
|
|
|
|
hlistLength :: HList r -> Int
|
|
|
|
|
hlistLength = \case
|
|
|
|
|
HNil -> 0
|
|
|
|
|
HCons _ l -> 1 + hlistLength l
|
|
|
|
|
|
2026-01-04 02:14:35 +00:00
|
|
|
data HListF (f :: Type -> Type) (as :: List Type) :: Type where
|
|
|
|
|
HNilF :: HListF f '[]
|
|
|
|
|
HConsF ::
|
|
|
|
|
f a ->
|
|
|
|
|
HListF f as ->
|
|
|
|
|
HListF f (a ': as)
|
2026-01-04 02:39:33 +00:00
|
|
|
foldHListF :: (forall x xs. f x -> r xs -> r (x ': xs)) -> r '[] -> HListF f as -> r as
|
|
|
|
|
foldHListF f e = \case
|
|
|
|
|
HNilF -> e
|
|
|
|
|
HConsF x xs -> f x $ foldHListF f e xs
|
2026-01-04 02:57:38 +00:00
|
|
|
foldHListF0 :: (forall x. f x -> r -> r) -> r -> HListF f as -> r
|
|
|
|
|
foldHListF0 f e = \case
|
|
|
|
|
HNilF -> e
|
|
|
|
|
HConsF x xs -> f x $ foldHListF0 f e xs
|
2026-01-04 02:14:35 +00:00
|
|
|
mapHListF :: (forall a. f a -> g a) -> HListF f as -> HListF g as
|
2026-01-04 02:39:33 +00:00
|
|
|
mapHListF t = foldHListF (\x r -> HConsF (t x) $ r) HNilF
|
2026-01-05 14:37:25 +00:00
|
|
|
hlistfLength :: HListF f r -> Int
|
|
|
|
|
hlistfLength = \case
|
|
|
|
|
HNilF -> 0
|
|
|
|
|
HConsF _ l -> 1 + hlistfLength l
|
2026-01-04 02:14:35 +00:00
|
|
|
|
|
|
|
|
newtype Fanout f g a = Fanout (f a, g a)
|
2026-01-05 14:37:25 +00:00
|
|
|
|
|
|
|
|
data TestTree m input where
|
|
|
|
|
TestTree :: TestName -> (input -> m output) -> [TestTree m output] -> TestTree m input
|
|
|
|
|
|
|
|
|
|
data TestResult
|
|
|
|
|
= Pass TestName [TestResult]
|
|
|
|
|
| Fail TestName SomeExceptionLegalShow
|
|
|
|
|
deriving (Show)
|
|
|
|
|
|
|
|
|
|
newtype TestName = TestName String
|
|
|
|
|
deriving newtype (IsString, Show)
|
|
|
|
|
|
|
|
|
|
mkTestName :: String -> TestName
|
|
|
|
|
mkTestName = TestName
|
|
|
|
|
|
|
|
|
|
newtype SomeExceptionLegalShow = SomeExceptionLegalShow SomeException
|
|
|
|
|
instance Show SomeExceptionLegalShow where
|
|
|
|
|
show (SomeExceptionLegalShow e) = show $ show e
|
|
|
|
|
|
|
|
|
|
getTestTree :: TestTree m r -> Tree TestName
|
|
|
|
|
getTestTree (TestTree name _ ts) = Node name $ map getTestTree ts
|
|
|
|
|
|
|
|
|
|
runTests :: (MonadCatch m) => a -> TestTree m a -> m TestResult
|
|
|
|
|
runTests x (TestTree name f ts) =
|
|
|
|
|
Control.Monad.Catch.try (f x) >>= \case
|
|
|
|
|
Left e ->
|
|
|
|
|
pure $ Fail name $ SomeExceptionLegalShow e
|
|
|
|
|
Right r ->
|
|
|
|
|
Pass name <$> for ts (runTests r)
|
|
|
|
|
|
|
|
|
|
assertEqual :: (Eq p, MonadFail f) => p -> p -> f ()
|
|
|
|
|
assertEqual expected actual = assert "not equal" (expected == actual)
|
|
|
|
|
assert :: (MonadFail f) => String -> Bool -> f ()
|
|
|
|
|
assert s b = if b then pure () else fail s
|
|
|
|
|
golden :: FilePath -> Text -> IO ()
|
|
|
|
|
golden p x = do
|
|
|
|
|
expected <- T.readFile p
|
|
|
|
|
if expected == x then pure () else fail "golden test failure"
|