chore: complete day 5

main
sgoudham 2 years ago
parent c6053d1f89
commit 9012b7b125
Signed by: hammy
GPG Key ID: 44E818FD5457EEA4

@ -91,5 +91,6 @@ library solutions
, base ^>=4.16.3.0 , base ^>=4.16.3.0
, containers ^>=0.6.5 , containers ^>=0.6.5
, text ^>=2.0 , text ^>=2.0
, split ^>=0.2.3
hs-source-dirs: solutions hs-source-dirs: solutions

@ -1,7 +1,20 @@
{-# language ScopedTypeVariables, GADTs #-}
module AOC ( module AOC (
module AOC.Types, module AOC.Types,
mkAocClient mkAocClient,
showSolution,
) where ) where
import AOC.Types import AOC.Types
import AOC.API import AOC.API
import Type.Reflection
-- TODO find a better way to avoid 'show'ing Stringlike things in
-- quotes ("") without resorting to end-users having to wrap Solutions
-- with something like data StringOr = StringLike a | NotStringLike b
showSolution :: forall a. (Typeable a, Show a) => a -> String
showSolution a = case eqTypeRep (typeRep @a) (typeRep @String) of
Just HRefl -> a
Nothing -> show a

@ -7,9 +7,11 @@ module AOC.Types (
import Data.Text (Text) import Data.Text (Text)
import GHC.Generics (Generic) import GHC.Generics (Generic)
import Web.Internal.FormUrlEncoded (ToForm) import Web.Internal.FormUrlEncoded (ToForm)
import Type.Reflection
data Solution where data Solution where
Solution :: Show b => (Text -> a) -> (a -> b) -> (a -> b) -> Solution Solution :: (Typeable b, Show b) =>
(Text -> a) -> (a -> b) -> (a -> b) -> Solution
data Submission = Submission { data Submission = Submission {
part :: Int, part :: Int,

@ -1,6 +1,6 @@
module Main where module Main where
import AOC (Solution (..), mkAocClient) import AOC (Solution (..), mkAocClient, showSolution)
import Configuration.Dotenv (defaultConfig, loadFile) import Configuration.Dotenv (defaultConfig, loadFile)
import Control.Exception (IOException, catch) import Control.Exception (IOException, catch)
import Control.Monad (when) import Control.Monad (when)
@ -71,8 +71,8 @@ run (Solution pInput part1 part2) part input =
Just n -> printf "Part %d: %s\n" n $ if n == 1 then part1' else part2' Just n -> printf "Part %d: %s\n" n $ if n == 1 then part1' else part2'
where where
parsed = pInput input parsed = pInput input
part1' = show $ part1 parsed part1' = showSolution $ part1 parsed
part2' = show $ part2 parsed part2' = showSolution $ part2 parsed
-- | CLI parser -- | CLI parser
opts :: ParserInfo Options opts :: ParserInfo Options

@ -2,7 +2,7 @@
module Tests (test) where module Tests (test) where
import AOC (Solution (..)) import AOC (Solution (..), showSolution)
import Data.Text qualified as T import Data.Text qualified as T
import Data.Text.IO qualified as T import Data.Text.IO qualified as T
import Data.Void (Void) import Data.Void (Void)
@ -14,6 +14,7 @@ import Test.Tasty.Ingredients.Basic (consoleTestReporter)
import Text.Megaparsec import Text.Megaparsec
import Text.Megaparsec.Char (newline, space) import Text.Megaparsec.Char (newline, space)
import Text.Megaparsec.Char.Lexer qualified as L import Text.Megaparsec.Char.Lexer qualified as L
import Type.Reflection
data TestInput = TestInput data TestInput = TestInput
{ _testName :: T.Text, { _testName :: T.Text,
@ -55,23 +56,23 @@ test (Solution pInput part1 part2) day part = do
-- | Given an input parser, part1 or part2 function, and a test input, -- | Given an input parser, part1 or part2 function, and a test input,
-- generate an HUnit test. -- generate an HUnit test.
mkTest :: Show b => (T.Text -> a) -> (a -> b) -> TestInput -> TestTree -- TODO this feels like a leaky abstraction of Solution
mkTest :: (Typeable b, Show b) => (T.Text -> a) -> (a -> b) -> TestInput -> TestTree
mkTest pInput part TestInput {..} = mkTest pInput part TestInput {..} =
testCase (T.unpack _testName) $ T.unpack _testExpected @=? result testCase (T.unpack _testName) $ T.unpack _testExpected @=? result
where where
result = show . part $ pInput _testInput result = showSolution . part $ pInput _testInput
-- | Parse test files into TestInput's -- | Parse test files into TestInput's
-- TODO more robust parsing and better, user-friendly custom errors
pTests :: Parser [TestInput] pTests :: Parser [TestInput]
pTests = many pTest <* eof pTests = many pTest <* eof
where where
pTest = TestInput <$> pName <*> pInput <*> pExpected <?> "Test" pTest = TestInput <$> pName <*> pInput <*> pExpected <?> "Test"
pName = T.pack <$> (symbol "-" *> lexeme (some (anySingleBut '\n'))) <?> "Test Name" -- TODO handle leading space in test input more elegantly
pName = T.pack <$> (symbol "-" *> some (anySingleBut '\n') <* newline) <?> "Test Name"
pInput = T.pack <$> someTill anySingle (symbol "==") <?> "Input Lines" pInput = T.pack <$> someTill anySingle (symbol "==") <?> "Input Lines"
pExpected = T.pack <$> many (anySingleBut '\n') <* newline <?> "Expected Output" pExpected = T.pack <$> many (anySingleBut '\n') <* newline <?> "Expected Output"
lexeme :: Parser a -> Parser a
lexeme = L.lexeme space
symbol :: T.Text -> Parser T.Text symbol :: T.Text -> Parser T.Text
symbol = L.symbol space symbol = L.symbol space

@ -1,16 +1,62 @@
{-# LANGUAGE OverloadedStrings #-}
module Days.Day05 (day05) where module Days.Day05 (day05) where
import AOC (Solution (..)) import AOC (Solution (..))
import qualified Data.Text as T import Data.Char
import Data.List qualified as L
import Data.List.Split qualified as S
import Data.Text qualified as T
-- 1. How many crates to move
-- 2. The stack to move crates from
-- 3. The stack to move crates to
type Instruction = (Int, Int, Int)
mapTuple :: (a -> b) -> (a, a, a) -> (b, b, b)
mapTuple f (a1, a2, a3) = (f a1, f a2, f a3)
replaceNth :: Int -> a -> [a] -> [a]
replaceNth _ _ [] = []
replaceNth n newVal (x : xs)
| n == 0 = newVal : xs
| otherwise = x : replaceNth (n - 1) newVal xs
day05 :: Solution day05 :: Solution
day05 = Solution parseInput part1 part2 day05 = Solution parseInput part1 part2
parseInput :: T.Text -> a parseInput :: T.Text -> ([String], [Instruction])
parseInput = error "parseInput not defined for day 05" parseInput input = (cleanStacks, intInsts)
where
[crates, insts] = S.splitOn "\n\n" (T.unpack input)
inst = filter (not . null) $ map (filter isDigit) (words insts)
intInsts = map (\[one, two, three] -> mapTuple read (one, two, three)) (S.chunksOf 3 inst)
stacks = init $ lines crates
cleanStacks = map concat $ L.transpose $ map (map filtered . S.chunksOf 4) stacks
filtered xs = [xs !! 1 | xs !! 1 /= ' ']
part1 :: a -> Int part1 :: ([String], [Instruction]) -> String
part1 = error "part1 not defined for day 05" part1 (stacks, instructions) = map head (foldl logic stacks instructions)
where
logic :: [String] -> Instruction -> [String]
logic stacks' (num, from, to) = replacedStacks
where
to' = to - 1
from' = from - 1
sFrom = stacks' !! from'
sTo = stacks' !! to'
taken = replaceNth from' (drop num sFrom) stacks'
replacedStacks = replaceNth to' (reverse (take num sFrom) ++ sTo) taken
part2 :: a -> Int part2 :: ([String], [Instruction]) -> String
part2 = error "part2 not defined for day 05" part2 (stacks, instructions) = map head (foldl logic stacks instructions)
where
logic :: [String] -> Instruction -> [String]
logic stacks' (num, from, to) = replacedStacks
where
to' = to - 1
from' = from - 1
sFrom = stacks' !! from'
sTo = stacks' !! to'
taken = replaceNth from' (drop num sFrom) stacks'
replacedStacks = replaceNth to' (take num sFrom ++ sTo) taken

Loading…
Cancel
Save