feat: finished day 2
This commit is contained in:
parent
de0f1dfb2e
commit
6622977d25
11 changed files with 418 additions and 0 deletions
126
2023/aoc2023.cabal
Normal file
126
2023/aoc2023.cabal
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
cabal-version: 3.0
|
||||
-- The cabal-version field refers to the version of the .cabal specification,
|
||||
-- and can be different from the cabal-install (the tool) version and the
|
||||
-- Cabal (the library) version you are using. As such, the Cabal (the library)
|
||||
-- version used must be equal or greater than the version stated in this field.
|
||||
-- Starting from the specification version 2.2, the cabal-version field must be
|
||||
-- the first thing in the cabal file.
|
||||
|
||||
-- Initial package description 'aoc2023' generated by
|
||||
-- 'cabal init'. For further documentation, see:
|
||||
-- http://haskell.org/cabal/users-guide/
|
||||
--
|
||||
-- The name of the package.
|
||||
name: aoc2023
|
||||
|
||||
-- The package version.
|
||||
-- See the Haskell package versioning policy (PVP) for standards
|
||||
-- guiding when and how versions should be incremented.
|
||||
-- https://pvp.haskell.org
|
||||
-- PVP summary: +-+------- breaking API changes
|
||||
-- | | +----- non-breaking API additions
|
||||
-- | | | +--- code changes with no API change
|
||||
version: 0.1.0.0
|
||||
|
||||
-- A short (one-line) description of the package.
|
||||
-- synopsis:
|
||||
|
||||
-- A longer description of the package.
|
||||
-- description:
|
||||
|
||||
-- The license under which the package is released.
|
||||
-- license:
|
||||
|
||||
-- The file containing the license text.
|
||||
-- license-file:
|
||||
|
||||
-- The package author(s).
|
||||
author: C4 Patino
|
||||
|
||||
-- An email address to which users can send suggestions, bug reports, and patches.
|
||||
maintainer: c4patino@gmail.com
|
||||
|
||||
-- A copyright notice.
|
||||
-- copyright:
|
||||
|
||||
build-type: Simple
|
||||
|
||||
-- Extra doc files to be distributed with the package, such as a CHANGELOG or a README.
|
||||
-- extra-doc-files:
|
||||
|
||||
-- Extra source files to be distributed with the package, such as examples, or a tutorial module.
|
||||
-- extra-source-files:
|
||||
|
||||
common warnings
|
||||
ghc-options: -Wall
|
||||
|
||||
library
|
||||
-- Import common warning flags.
|
||||
import: warnings
|
||||
|
||||
-- Modules exported by the library.
|
||||
exposed-modules: Day01, Day02
|
||||
|
||||
-- Modules included in this library but not exported.
|
||||
-- other-modules:
|
||||
|
||||
-- LANGUAGE extensions used by modules in this package.
|
||||
-- other-extensions:
|
||||
|
||||
-- Other library packages from which modules are imported.
|
||||
build-depends: base ^>=4.19.2.0
|
||||
|
||||
-- Directories containing source files.
|
||||
hs-source-dirs: src
|
||||
|
||||
-- Base language which the package is written in.
|
||||
default-language: Haskell2010
|
||||
|
||||
executable aoc2023
|
||||
-- Import common warning flags.
|
||||
import: warnings
|
||||
|
||||
-- .hs or .lhs file containing the Main module.
|
||||
main-is: Main.hs
|
||||
|
||||
-- Modules included in this executable, other than Main.
|
||||
-- other-modules:
|
||||
|
||||
-- LANGUAGE extensions used by modules in this package.
|
||||
-- other-extensions:
|
||||
|
||||
-- Other library packages from which modules are imported.
|
||||
build-depends:
|
||||
base ^>=4.19.2.0,
|
||||
aoc2023
|
||||
|
||||
-- Directories containing source files.
|
||||
hs-source-dirs: app
|
||||
|
||||
-- Base language which the package is written in.
|
||||
default-language: Haskell2010
|
||||
|
||||
test-suite aoc2023-test
|
||||
-- Import common warning flags.
|
||||
import: warnings
|
||||
|
||||
-- Base language which the package is written in.
|
||||
default-language: Haskell2010
|
||||
|
||||
-- Modules included in this executable, other than Main.
|
||||
other-modules: Common.Tests, Day01Spec, Day02Spec
|
||||
|
||||
-- LANGUAGE extensions used by modules in this package.
|
||||
-- other-extensions:
|
||||
|
||||
-- The interface type and version of the test suite.
|
||||
type: exitcode-stdio-1.0
|
||||
|
||||
-- Directories containing source files.
|
||||
hs-source-dirs: test
|
||||
|
||||
-- The entrypoint to the test suite.
|
||||
main-is: Spec.hs
|
||||
|
||||
-- Test dependencies.
|
||||
build-depends: base ^>=4.19.2.0, hspec, hspec-contrib, QuickCheck, HUnit, aoc2023
|
||||
60
2023/app/Main.hs
Normal file
60
2023/app/Main.hs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
module Main where
|
||||
|
||||
import qualified Day01
|
||||
import qualified Day02
|
||||
import System.Console.GetOpt
|
||||
import System.Environment (getArgs)
|
||||
import System.Exit (exitFailure)
|
||||
|
||||
data Options = Options {optFile :: Maybe FilePath, optHelp :: Bool} deriving (Show)
|
||||
|
||||
defaultOptions :: Options
|
||||
defaultOptions = Options {optFile = Nothing, optHelp = False}
|
||||
|
||||
options :: [OptDescr (Options -> Options)]
|
||||
options =
|
||||
[ Option
|
||||
['f']
|
||||
["file"]
|
||||
(ReqArg (\f opts -> opts {optFile = Just f}) "FILE")
|
||||
"input file to use",
|
||||
Option
|
||||
['h']
|
||||
["help"]
|
||||
(NoArg (\opts -> opts {optHelp = True}))
|
||||
"show help"
|
||||
]
|
||||
|
||||
runDay :: Int -> FilePath -> IO (String, String)
|
||||
runDay 1 filename = do
|
||||
(a, b) <- Day01.run filename
|
||||
return (show a, show b)
|
||||
runDay 2 filename = do
|
||||
(a, b) <- Day02.run filename
|
||||
return (show a, show b)
|
||||
runDay _ _ = return ("Not implemented", "Not implemented")
|
||||
|
||||
defaultFilename :: Int -> FilePath
|
||||
defaultFilename day = "inputs/day" ++ (if day < 10 then "0" else "") ++ show day ++ "/input.txt"
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
args <- getArgs
|
||||
case getOpt Permute options args of
|
||||
(opts, [dayStr], []) -> do
|
||||
let day = read dayStr
|
||||
finalOpts = foldl (flip id) defaultOptions opts
|
||||
filename = case optFile finalOpts of
|
||||
Just f -> f
|
||||
Nothing -> defaultFilename day
|
||||
(part1Answer, part2Answer) <- runDay day filename
|
||||
putStrLn $ "Part 1: " ++ part1Answer
|
||||
putStrLn $ "Part 2: " ++ part2Answer
|
||||
(_, [], []) -> do
|
||||
putStrLn "Error: Day number is required"
|
||||
putStrLn $ usageInfo "Usage: aoc2023 <day> [OPTIONS]" options
|
||||
exitFailure
|
||||
(_, _, errs) -> do
|
||||
putStrLn $ concat errs
|
||||
putStrLn $ usageInfo "Usage: aoc2023 <day> [OPTIONS]" options
|
||||
exitFailure
|
||||
4
2023/inputs/day01/part1.txt
Normal file
4
2023/inputs/day01/part1.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
1abc2
|
||||
pqr3stu8vwx
|
||||
a1b2c3d4e5f
|
||||
treb7uchet
|
||||
7
2023/inputs/day01/part2.txt
Normal file
7
2023/inputs/day01/part2.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
two1nine
|
||||
eightwothree
|
||||
abcone2threexyz
|
||||
xtwone3four
|
||||
4nineeightseven2
|
||||
zoneight234
|
||||
7pqrstsixteen
|
||||
5
2023/inputs/day02/example.txt
Normal file
5
2023/inputs/day02/example.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green
|
||||
Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue
|
||||
Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red
|
||||
Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red
|
||||
Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green
|
||||
42
2023/src/Day01.hs
Normal file
42
2023/src/Day01.hs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
module Day01 (part1, part2, run) where
|
||||
|
||||
import Data.Char (isDigit)
|
||||
import Data.List (isPrefixOf)
|
||||
import Data.Maybe (maybeToList)
|
||||
|
||||
part1 :: [String] -> Int
|
||||
part1 = sum . map (extractValue . filter isDigit)
|
||||
|
||||
part2 :: [String] -> Int
|
||||
part2 = sum . map (extractValue . collectDigits)
|
||||
|
||||
run :: FilePath -> IO (Int, Int)
|
||||
run filename =
|
||||
readFile filename >>= \contents ->
|
||||
let parsed = lines contents
|
||||
in return (part1 parsed, part2 parsed)
|
||||
|
||||
collectDigits :: String -> String
|
||||
collectDigits [] = []
|
||||
collectDigits s@(c : cs)
|
||||
| isDigit c = c : collectDigits cs
|
||||
| otherwise = maybeToList (spelledDigitToChar s) ++ collectDigits cs
|
||||
|
||||
extractValue :: String -> Int
|
||||
extractValue [] = 0
|
||||
extractValue s@(d : _) = read [d, last s]
|
||||
|
||||
spelledDigitToChar :: String -> Maybe Char
|
||||
spelledDigitToChar s = lookup True matches
|
||||
where
|
||||
matches =
|
||||
[ ("one" `isPrefixOf` s, '1'),
|
||||
("two" `isPrefixOf` s, '2'),
|
||||
("three" `isPrefixOf` s, '3'),
|
||||
("four" `isPrefixOf` s, '4'),
|
||||
("five" `isPrefixOf` s, '5'),
|
||||
("six" `isPrefixOf` s, '6'),
|
||||
("seven" `isPrefixOf` s, '7'),
|
||||
("eight" `isPrefixOf` s, '8'),
|
||||
("nine" `isPrefixOf` s, '9')
|
||||
]
|
||||
76
2023/src/Day02.hs
Normal file
76
2023/src/Day02.hs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
module Day02 (part1, part2, run) where
|
||||
|
||||
import Text.Read (readMaybe)
|
||||
|
||||
data Game = Game {id :: Int, draw :: Draw} deriving (Show)
|
||||
|
||||
data Draw = Draw {red :: Int, green :: Int, blue :: Int} deriving (Show)
|
||||
|
||||
part1 :: [Game] -> Int
|
||||
part1 = sum . map gid . filter possible
|
||||
where
|
||||
gid (Game i _) = i
|
||||
|
||||
part2 :: [Game] -> Int
|
||||
part2 = sum . map power
|
||||
where
|
||||
power (Game _ (Draw r g b)) = r * g * b
|
||||
|
||||
run :: FilePath -> IO (Int, Int)
|
||||
run filename = do
|
||||
input <- readFile filename
|
||||
let games = parseGames input
|
||||
return (part1 games, part2 games)
|
||||
|
||||
instance Semigroup Draw where
|
||||
(Draw a b c) <> (Draw x y z) = Draw (max a x) (max b y) (max c z)
|
||||
|
||||
instance Monoid Draw where
|
||||
mempty = Draw 0 0 0
|
||||
|
||||
possible :: Game -> Bool
|
||||
possible = valid . draw
|
||||
where
|
||||
valid (Draw r g b) = r <= 12 && g <= 13 && b <= 14
|
||||
|
||||
parseGames :: String -> [Game]
|
||||
parseGames input = map parseGame $ lines input
|
||||
|
||||
parseGame :: String -> Game
|
||||
parseGame s =
|
||||
let (header, rest) = break (== ':') s
|
||||
gameId = read (drop 5 header) :: Int
|
||||
drawsStr = drop 2 rest
|
||||
draws = parseDraws drawsStr
|
||||
in Game gameId draws
|
||||
|
||||
parseDraws :: String -> Draw
|
||||
parseDraws s =
|
||||
let drawParts = splitOn ';' s
|
||||
in foldl (<>) mempty (map parseDraw drawParts)
|
||||
|
||||
parseDraw :: String -> Draw
|
||||
parseDraw s =
|
||||
let counts = splitOn ',' s
|
||||
in foldl (<>) mempty (map parseCount counts)
|
||||
|
||||
parseCount :: String -> Draw
|
||||
parseCount s =
|
||||
case words s of
|
||||
[nStr, color] ->
|
||||
case (readMaybe nStr :: Maybe Int) of
|
||||
Just i -> case color of
|
||||
"red" -> Draw i 0 0
|
||||
"green" -> Draw 0 i 0
|
||||
"blue" -> Draw 0 0 i
|
||||
_ -> mempty
|
||||
Nothing -> mempty
|
||||
_ -> mempty
|
||||
|
||||
splitOn :: Char -> String -> [String]
|
||||
splitOn _ [] = []
|
||||
splitOn delim str =
|
||||
let (first, rest) = break (== delim) str
|
||||
in first : case rest of
|
||||
[] -> []
|
||||
(_ : rest') -> splitOn delim rest'
|
||||
44
2023/test/Common/Tests.hs
Normal file
44
2023/test/Common/Tests.hs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
module Common.Tests where
|
||||
|
||||
import Test.Hspec
|
||||
|
||||
-- | Represents a single test case with an input file and its expected output.
|
||||
data TestCase a = TestCase
|
||||
{ -- | Path to the input file for the test case.
|
||||
filename :: FilePath,
|
||||
-- | Expected output value for the test case, of any type 'a'.
|
||||
expectedOutput :: a
|
||||
}
|
||||
|
||||
-- | Run a suite of test cases by applying a runner function to input files,
|
||||
-- then projecting a part of the result for comparison against expected outputs.
|
||||
--
|
||||
-- Parameters:
|
||||
--
|
||||
-- * @label@: parent label for the specification.
|
||||
-- * @projector@: function to extract a single value from a tuple (e.g., part 1 or part 2).
|
||||
-- * @runner@: function under test, which takes a filename and returns an IO action producing a tuple of outputs.
|
||||
-- * @testCases@: list of test cases with input files and expected results.
|
||||
--
|
||||
-- Returns an Hspec 'Spec' running all test cases with descriptive labels and assertions.
|
||||
mkPartSpec ::
|
||||
(Eq a, Show a) =>
|
||||
-- | parent label for the specification
|
||||
String ->
|
||||
-- | projector from a tuple to the value to test
|
||||
((a, a) -> a) ->
|
||||
-- | function under test (runner)
|
||||
(FilePath -> IO (a, a)) ->
|
||||
-- | list of test cases with input files and expected outputs
|
||||
[TestCase a] ->
|
||||
-- | resulting Hspec Spec with all tests
|
||||
Spec
|
||||
mkPartSpec label projector runner testCases =
|
||||
describe label $
|
||||
mapM_
|
||||
( \(TestCase file expected) ->
|
||||
it ("run " ++ file ++ " returns " ++ show expected) $ do
|
||||
result <- runner file
|
||||
projector result `shouldBe` expected
|
||||
)
|
||||
testCases
|
||||
22
2023/test/Day01Spec.hs
Normal file
22
2023/test/Day01Spec.hs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
module Day01Spec (spec) where
|
||||
|
||||
import Common.Tests (TestCase (..), mkPartSpec)
|
||||
import Day01 (run)
|
||||
import Test.Hspec
|
||||
|
||||
part1TestCases :: [TestCase Int]
|
||||
part1TestCases =
|
||||
[ TestCase "inputs/day01/part1.txt" 142,
|
||||
TestCase "inputs/day01/input.txt" 55108
|
||||
]
|
||||
|
||||
part2TestCases :: [TestCase Int]
|
||||
part2TestCases =
|
||||
[ TestCase "inputs/day01/part2.txt" 281,
|
||||
TestCase "inputs/day01/input.txt" 56324
|
||||
]
|
||||
|
||||
spec :: Spec
|
||||
spec = do
|
||||
mkPartSpec "Day 1/1 tests" fst run part1TestCases
|
||||
mkPartSpec "Day 1/2 tests" snd run part2TestCases
|
||||
22
2023/test/Day02Spec.hs
Normal file
22
2023/test/Day02Spec.hs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
module Day02Spec (spec) where
|
||||
|
||||
import Common.Tests (TestCase (..), mkPartSpec)
|
||||
import Day02 (run)
|
||||
import Test.Hspec
|
||||
|
||||
part1TestCases :: [TestCase Int]
|
||||
part1TestCases =
|
||||
[ TestCase "inputs/day02/example.txt" 8,
|
||||
TestCase "inputs/day02/input.txt" 2617
|
||||
]
|
||||
|
||||
part2TestCases :: [TestCase Int]
|
||||
part2TestCases =
|
||||
[ TestCase "inputs/day02/example.txt" 2286,
|
||||
TestCase "inputs/day02/input.txt" 59795
|
||||
]
|
||||
|
||||
spec :: Spec
|
||||
spec = do
|
||||
mkPartSpec "Day 2/1 tests" fst run part1TestCases
|
||||
mkPartSpec "Day 2/2 tests" snd run part2TestCases
|
||||
10
2023/test/Spec.hs
Normal file
10
2023/test/Spec.hs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
module Main (main) where
|
||||
|
||||
import qualified Day01Spec
|
||||
import qualified Day02Spec
|
||||
import Test.Hspec
|
||||
|
||||
main :: IO ()
|
||||
main = hspec $ do
|
||||
Day01Spec.spec
|
||||
Day02Spec.spec
|
||||
Loading…
Add table
Add a link
Reference in a new issue