Allow output types to vary for different parts of same day
For now this applies to Haskell only, and it may turn out to be tricky for the Rust implementation. In practice, the limitation hasn't turned out to be important, and we could even go the other way and use `Integer` everywhere. This does however at least help with debugging, as well as just being conceptually right. The `nil` and `(/\)` functions are intended to be overloaded to work for other list-like things in a later commit, and from there we will investigate using `OverloadedLists` and `RebindableSyntax` to recover standard list syntax, although there are probably limitations due to `(:)` being special.
This commit is contained in:
parent
6ca7b4eac8
commit
415055dcc2
@ -45,7 +45,7 @@ main =
|
|||||||
in
|
in
|
||||||
withResource (parseFile $ "../inputs/" <> t <> "/" <> pt) mempty \input ->
|
withResource (parseFile $ "../inputs/" <> t <> "/" <> pt) mempty \input ->
|
||||||
testGroup pt $
|
testGroup pt $
|
||||||
( zip (map show [1 :: Int ..]) parts <&> \(n, pp) ->
|
( flip mapWithIndexOutputParameterisedFunctionList parts \(show . succ -> n) pp ->
|
||||||
goldenVsStringDiff n diffCommand ("../outputs/" <> t <> "/" <> pt <> "/" <> n) $
|
goldenVsStringDiff n diffCommand ("../outputs/" <> t <> "/" <> pt <> "/" <> n) $
|
||||||
TL.encodeUtf8 . (<> "\n") . TL.show . pp <$> input
|
TL.encodeUtf8 . (<> "\n") . TL.show . pp <$> input
|
||||||
)
|
)
|
||||||
|
|||||||
@ -42,6 +42,11 @@ module Pre (
|
|||||||
adjacentPairs,
|
adjacentPairs,
|
||||||
sortPair,
|
sortPair,
|
||||||
diffCommand,
|
diffCommand,
|
||||||
|
OutputParameterisedFunctionList,
|
||||||
|
mapOutputParameterisedFunctionList,
|
||||||
|
mapWithIndexOutputParameterisedFunctionList,
|
||||||
|
(/\),
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
@ -69,7 +74,8 @@ import Data.Foldable hiding (foldl1, foldr1, maximum, maximumBy, minimum, minimu
|
|||||||
import Data.Foldable1
|
import Data.Foldable1
|
||||||
import Data.Function
|
import Data.Function
|
||||||
import Data.Functor
|
import Data.Functor
|
||||||
import Data.List (sortOn, transpose)
|
import Data.Kind (Constraint, Type)
|
||||||
|
import Data.List (List, sortOn, transpose)
|
||||||
import Data.List.Extra (dropEnd, enumerate, firstJust, notNull, splitOn)
|
import Data.List.Extra (dropEnd, enumerate, firstJust, notNull, splitOn)
|
||||||
import Data.List.NonEmpty (NonEmpty ((:|)), nonEmpty, some1, tail, tails)
|
import Data.List.NonEmpty (NonEmpty ((:|)), nonEmpty, some1, tail, tails)
|
||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
@ -91,10 +97,10 @@ import Text.Megaparsec hiding (Pos, State, Stream, many, some)
|
|||||||
import Text.Megaparsec.Char
|
import Text.Megaparsec.Char
|
||||||
import Text.Megaparsec.Char.Lexer (decimal)
|
import Text.Megaparsec.Char.Lexer (decimal)
|
||||||
|
|
||||||
data Puzzle = forall input output. (Show output) => Puzzle
|
data Puzzle = forall input outputs. Puzzle
|
||||||
{ number :: Word
|
{ number :: Word
|
||||||
, parser :: Bool -> Parsec Void Text input
|
, parser :: Bool -> Parsec Void Text input
|
||||||
, parts :: [input -> output]
|
, parts :: OutputParameterisedFunctionList Show input outputs
|
||||||
, extraTests :: Bool -> FilePath -> IO input -> [TestTree]
|
, extraTests :: Bool -> FilePath -> IO input -> [TestTree]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,3 +131,35 @@ sortPair (a, b) = if a <= b then (a, b) else (b, a)
|
|||||||
|
|
||||||
diffCommand :: FilePath -> FilePath -> [String]
|
diffCommand :: FilePath -> FilePath -> [String]
|
||||||
diffCommand a b = ["diff", "--color=always", a, b]
|
diffCommand a b = ["diff", "--color=always", a, b]
|
||||||
|
|
||||||
|
infixr 9 /\
|
||||||
|
(/\) :: (c output) => (input -> output) -> OutputParameterisedFunctionList c input outputs -> OutputParameterisedFunctionList c input (output : outputs)
|
||||||
|
(/\) = OutputParameterisedFunctionListCons
|
||||||
|
nil :: OutputParameterisedFunctionList c input '[]
|
||||||
|
nil = OutputParameterisedFunctionListNil
|
||||||
|
|
||||||
|
data OutputParameterisedFunctionList (c :: Type -> Constraint) (input :: Type) (outputs :: List Type) :: Type where
|
||||||
|
OutputParameterisedFunctionListNil :: OutputParameterisedFunctionList c input '[]
|
||||||
|
OutputParameterisedFunctionListCons ::
|
||||||
|
(c output) =>
|
||||||
|
(input -> output) ->
|
||||||
|
OutputParameterisedFunctionList c input outputs ->
|
||||||
|
OutputParameterisedFunctionList c input (output ': outputs)
|
||||||
|
mapOutputParameterisedFunctionList ::
|
||||||
|
(forall output. (c output) => (input -> output) -> a) ->
|
||||||
|
OutputParameterisedFunctionList c input outputs ->
|
||||||
|
[a]
|
||||||
|
mapOutputParameterisedFunctionList f = \case
|
||||||
|
OutputParameterisedFunctionListNil -> []
|
||||||
|
OutputParameterisedFunctionListCons x xs -> f x : mapOutputParameterisedFunctionList f xs
|
||||||
|
mapWithIndexOutputParameterisedFunctionList ::
|
||||||
|
forall c input outputs a.
|
||||||
|
(forall output. (c output) => Int -> (input -> output) -> a) ->
|
||||||
|
OutputParameterisedFunctionList c input outputs ->
|
||||||
|
[a]
|
||||||
|
mapWithIndexOutputParameterisedFunctionList f = go 0
|
||||||
|
where
|
||||||
|
go :: Int -> OutputParameterisedFunctionList c input outputs' -> [a]
|
||||||
|
go i = \case
|
||||||
|
OutputParameterisedFunctionListNil -> []
|
||||||
|
OutputParameterisedFunctionListCons x xs -> f i x : go (i + 1) xs
|
||||||
|
|||||||
@ -8,13 +8,15 @@ puzzle =
|
|||||||
{ number = 1
|
{ number = 1
|
||||||
, parser = const $ ((,) <$> ((char 'L' $> L) <|> (char 'R' $> R)) <*> (Inc <$> decimal)) `sepEndBy` newline
|
, parser = const $ ((,) <$> ((char 'L' $> L) <|> (char 'R' $> R)) <*> (Inc <$> decimal)) `sepEndBy` newline
|
||||||
, parts =
|
, parts =
|
||||||
[ sum
|
( sum
|
||||||
. flip evalState 50
|
. ( flip evalState 50
|
||||||
. traverse \(d, i) -> do
|
. traverse \(d, i) -> do
|
||||||
modify $ snd . step i d
|
modify $ snd . step i d
|
||||||
p' <- get
|
p' <- get
|
||||||
pure $ Count if p' == 0 then 1 else 0
|
pure $ Count if p' == 0 then 1 else 0
|
||||||
, sum
|
)
|
||||||
|
)
|
||||||
|
/\ ( sum
|
||||||
. flip evalState 50
|
. flip evalState 50
|
||||||
. traverse \(d, i) -> do
|
. traverse \(d, i) -> do
|
||||||
p <- get
|
p <- get
|
||||||
@ -27,7 +29,8 @@ puzzle =
|
|||||||
| p == 0 -> abs c - 1
|
| p == 0 -> abs c - 1
|
||||||
| p' == 0 -> abs c + 1
|
| p' == 0 -> abs c + 1
|
||||||
| otherwise -> abs c
|
| otherwise -> abs c
|
||||||
]
|
)
|
||||||
|
/\ nil
|
||||||
, extraTests = mempty
|
, extraTests = mempty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -23,12 +23,13 @@ puzzle =
|
|||||||
void $ single '}'
|
void $ single '}'
|
||||||
pure (Lights $ IM.fromList $ zip [0 ..] lights, switches)
|
pure (Lights $ IM.fromList $ zip [0 ..] lights, switches)
|
||||||
, parts =
|
, parts =
|
||||||
[ sum . map \(lights, switches) ->
|
( sum . map \(lights, switches) ->
|
||||||
maybe (error "no solution") length
|
maybe (error "no solution") length
|
||||||
. firstJust (firstJust (\(s, ls) -> guard (allOff ls) $> s))
|
. firstJust (firstJust (\(s, ls) -> guard (allOff ls) $> s))
|
||||||
$ flip iterate [([], lights)] \ls ->
|
$ flip iterate [([], lights)] \ls ->
|
||||||
concatMap (\s -> map (\(ss, l) -> (s : ss, applySwitch s l)) ls) switches
|
concatMap (\s -> map (\(ss, l) -> (s : ss, applySwitch s l)) ls) switches
|
||||||
]
|
)
|
||||||
|
/\ nil
|
||||||
, extraTests = mempty
|
, extraTests = mempty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,13 +10,15 @@ puzzle =
|
|||||||
{ number = 2
|
{ number = 2
|
||||||
, parser = const $ (<* newline) $ ((,) <$> (decimal <* char '-') <*> decimal) `sepBy` (char ',')
|
, parser = const $ (<* newline) $ ((,) <$> (decimal <* char '-') <*> decimal) `sepBy` (char ',')
|
||||||
, parts =
|
, parts =
|
||||||
[ sum
|
( sum
|
||||||
. concatMap
|
. concatMap
|
||||||
(mapMaybe (\n -> guard (isRepetition2 n) $> n) . uncurry enumFromTo)
|
(mapMaybe (\n -> guard (isRepetition2 n) $> n) . uncurry enumFromTo)
|
||||||
, sum
|
)
|
||||||
|
/\ ( sum
|
||||||
. concatMap
|
. concatMap
|
||||||
(mapMaybe (\n -> guard (isRepetitionN n) $> n) . uncurry enumFromTo)
|
(mapMaybe (\n -> guard (isRepetitionN n) $> n) . uncurry enumFromTo)
|
||||||
]
|
)
|
||||||
|
/\ nil
|
||||||
, extraTests = mempty
|
, extraTests = mempty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,11 +10,13 @@ puzzle =
|
|||||||
{ number = 3
|
{ number = 3
|
||||||
, parser = const $ (Bank <$> some1 digit) `sepEndBy` newline
|
, parser = const $ (Bank <$> some1 digit) `sepEndBy` newline
|
||||||
, parts =
|
, parts =
|
||||||
[ sum
|
( sum
|
||||||
. map (digitsToInt . fromMaybe (error "battery list too short") . maxBatteries 2)
|
. map (digitsToInt . fromMaybe (error "battery list too short") . maxBatteries 2)
|
||||||
, sum
|
)
|
||||||
|
/\ ( sum
|
||||||
. map (digitsToInt . fromMaybe (error "battery list too short") . maxBatteries 12)
|
. map (digitsToInt . fromMaybe (error "battery list too short") . maxBatteries 12)
|
||||||
]
|
)
|
||||||
|
/\ nil
|
||||||
, extraTests = mempty
|
, extraTests = mempty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,11 +14,13 @@ puzzle =
|
|||||||
{ number = 4
|
{ number = 4
|
||||||
, parser = const $ (some $ asum $ enumerate <&> \t -> char (inToChar t) $> t) `sepEndBy` newline
|
, parser = const $ (some $ asum $ enumerate <&> \t -> char (inToChar t) $> t) `sepEndBy` newline
|
||||||
, parts =
|
, parts =
|
||||||
[ (\g -> countRolls g - countRolls (removeAccessibleRolls $ findAccessible g))
|
( (\g -> countRolls g - countRolls (removeAccessibleRolls $ findAccessible g))
|
||||||
. mkGrid
|
. mkGrid
|
||||||
, (\g -> countRolls g - countRolls (fst $ S.head $ S.filter (noneAccessible . snd) $ generateFrames g))
|
)
|
||||||
|
/\ ( (\g -> countRolls g - countRolls (fst $ S.head $ S.filter (noneAccessible . snd) $ generateFrames g))
|
||||||
. mkGrid
|
. mkGrid
|
||||||
]
|
)
|
||||||
|
/\ nil
|
||||||
, extraTests = \isRealData path input ->
|
, extraTests = \isRealData path input ->
|
||||||
[ testCase "round trip" do
|
[ testCase "round trip" do
|
||||||
t <- TL.readFile if isRealData then "../inputs/real/4" else "../inputs/examples/4"
|
t <- TL.readFile if isRealData then "../inputs/real/4" else "../inputs/examples/4"
|
||||||
|
|||||||
@ -12,16 +12,18 @@ puzzle =
|
|||||||
vals <- decimal `sepEndBy` newline
|
vals <- decimal `sepEndBy` newline
|
||||||
pure (ranges, vals)
|
pure (ranges, vals)
|
||||||
, parts =
|
, parts =
|
||||||
[ \(ranges, vals) ->
|
( \(ranges, vals) ->
|
||||||
length
|
length
|
||||||
. filter (flip any ranges . isInRange)
|
. filter (flip any ranges . isInRange)
|
||||||
$ vals
|
$ vals
|
||||||
, sum
|
)
|
||||||
|
/\ ( sum
|
||||||
. map rangeLength
|
. map rangeLength
|
||||||
. foldr addInterval []
|
. foldr addInterval []
|
||||||
. sortOn (Down . (.lower))
|
. sortOn (Down . (.lower))
|
||||||
. fst
|
. fst
|
||||||
]
|
)
|
||||||
|
/\ nil
|
||||||
, extraTests = mempty
|
, extraTests = mempty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,10 +12,11 @@ puzzle =
|
|||||||
void newline
|
void newline
|
||||||
pure (ops, ints)
|
pure (ops, ints)
|
||||||
, parts =
|
, parts =
|
||||||
[ sum
|
( sum
|
||||||
. uncurry (zipWith applyToList)
|
. uncurry (zipWith applyToList)
|
||||||
. second (transpose . map (map (digitsToInt @Int . catMaybes) . filter notNull . splitOn [Nothing]))
|
. second (transpose . map (map (digitsToInt @Int . catMaybes) . filter notNull . splitOn [Nothing]))
|
||||||
, sum
|
)
|
||||||
|
/\ ( sum
|
||||||
. uncurry (zipWith applyToList)
|
. uncurry (zipWith applyToList)
|
||||||
. second
|
. second
|
||||||
( map catMaybes
|
( map catMaybes
|
||||||
@ -23,7 +24,8 @@ puzzle =
|
|||||||
. map (\l -> if all isNothing l then Nothing else Just $ digitsToInt @Int $ catMaybes l)
|
. map (\l -> if all isNothing l then Nothing else Just $ digitsToInt @Int $ catMaybes l)
|
||||||
. transpose
|
. transpose
|
||||||
)
|
)
|
||||||
]
|
)
|
||||||
|
/\ nil
|
||||||
, extraTests = mempty
|
, extraTests = mempty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,7 @@ puzzle =
|
|||||||
let splitters = map (IS.fromList . map fst . filter snd . zip [0 ..]) rows
|
let splitters = map (IS.fromList . map fst . filter snd . zip [0 ..]) rows
|
||||||
pure (start, splitters)
|
pure (start, splitters)
|
||||||
, parts =
|
, parts =
|
||||||
[ uncurry \start ->
|
( uncurry \start ->
|
||||||
flip execState (0 :: Int)
|
flip execState (0 :: Int)
|
||||||
. foldlM
|
. foldlM
|
||||||
( \beams splitters ->
|
( \beams splitters ->
|
||||||
@ -27,7 +27,8 @@ puzzle =
|
|||||||
pure if hit then [x - 1, x + 1] else [x]
|
pure if hit then [x - 1, x + 1] else [x]
|
||||||
)
|
)
|
||||||
(IS.singleton start)
|
(IS.singleton start)
|
||||||
, uncurry \start ->
|
)
|
||||||
|
/\ ( uncurry \start ->
|
||||||
sum
|
sum
|
||||||
. map snd
|
. map snd
|
||||||
. IM.toList
|
. IM.toList
|
||||||
@ -38,6 +39,7 @@ puzzle =
|
|||||||
zip (if hit then [x - 1, x + 1] else [x]) (repeat n)
|
zip (if hit then [x - 1, x + 1] else [x]) (repeat n)
|
||||||
)
|
)
|
||||||
(IM.singleton start (1 :: Int))
|
(IM.singleton start (1 :: Int))
|
||||||
]
|
)
|
||||||
|
/\ nil
|
||||||
, extraTests = mempty
|
, extraTests = mempty
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,7 @@ puzzle =
|
|||||||
(if isRealData then 1000 else 10,)
|
(if isRealData then 1000 else 10,)
|
||||||
<$> (V3 <$> decimal <* single ',' <*> decimal <* single ',' <*> decimal) `sepEndBy` newline
|
<$> (V3 <$> decimal <* single ',' <*> decimal <* single ',' <*> decimal) `sepEndBy` newline
|
||||||
, parts =
|
, parts =
|
||||||
[ uncurry \n ->
|
( uncurry \n ->
|
||||||
product
|
product
|
||||||
. take 3
|
. take 3
|
||||||
. sortOn Down
|
. sortOn Down
|
||||||
@ -24,13 +24,15 @@ puzzle =
|
|||||||
. maybe (error "not enough boxes") snd
|
. maybe (error "not enough boxes") snd
|
||||||
. listIndex n
|
. listIndex n
|
||||||
. connectBoxes
|
. connectBoxes
|
||||||
, uncurry . const $
|
)
|
||||||
|
/\ ( uncurry . const $
|
||||||
uncurry ((*) `on` view _x)
|
uncurry ((*) `on` view _x)
|
||||||
. maybe (error "sets never unified") fst
|
. maybe (error "sets never unified") fst
|
||||||
. lastMay
|
. lastMay
|
||||||
. takeWhile ((> 1) . DS.sets . snd)
|
. takeWhile ((> 1) . DS.sets . snd)
|
||||||
. connectBoxes
|
. connectBoxes
|
||||||
]
|
)
|
||||||
|
/\ nil
|
||||||
, extraTests = mempty
|
, extraTests = mempty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,12 +8,13 @@ puzzle =
|
|||||||
{ number = 9
|
{ number = 9
|
||||||
, parser = const $ (V2 <$> decimal <* single ',' <*> decimal) `sepEndBy1` newline
|
, parser = const $ (V2 <$> decimal <* single ',' <*> decimal) `sepEndBy1` newline
|
||||||
, parts =
|
, parts =
|
||||||
[ maximum
|
( maximum
|
||||||
. fmap (squareSize . uncurry Rectangle)
|
. fmap (squareSize . uncurry Rectangle)
|
||||||
. fromMaybe (error "input too small")
|
. fromMaybe (error "input too small")
|
||||||
. nonEmpty
|
. nonEmpty
|
||||||
. allUnorderedPairs False
|
. allUnorderedPairs False
|
||||||
, \points ->
|
)
|
||||||
|
/\ ( \points ->
|
||||||
let path =
|
let path =
|
||||||
fromMaybe (error "malformed line")
|
fromMaybe (error "malformed line")
|
||||||
. traverse mkLine
|
. traverse mkLine
|
||||||
@ -26,7 +27,8 @@ puzzle =
|
|||||||
. sortOn (Down . snd)
|
. sortOn (Down . snd)
|
||||||
. fmap ((id &&& squareSize) . uncurry Rectangle)
|
. fmap ((id &&& squareSize) . uncurry Rectangle)
|
||||||
$ allUnorderedPairs False points
|
$ allUnorderedPairs False points
|
||||||
]
|
)
|
||||||
|
/\ nil
|
||||||
, extraTests = mempty
|
, extraTests = mempty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,7 @@ executable aoc
|
|||||||
default-extensions:
|
default-extensions:
|
||||||
BlockArguments
|
BlockArguments
|
||||||
DuplicateRecordFields
|
DuplicateRecordFields
|
||||||
|
ImpredicativeTypes
|
||||||
LexicalNegation
|
LexicalNegation
|
||||||
MultiWayIf
|
MultiWayIf
|
||||||
NamedFieldPuns
|
NamedFieldPuns
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user