refactor: Make implementation less redundant

This commit is contained in:
Victor Martinez 2025-03-12 00:33:14 +01:00
parent 36fbdd5864
commit 6e55a067dc

View file

@ -5,7 +5,7 @@ module Day1
where where
import Control.Exception (try) import Control.Exception (try)
import qualified Data.Bifunctor as Bifunctor import qualified Data.Bifunctor as BF
import Data.Either import Data.Either
import Data.List (sort) import Data.List (sort)
import Text.Read (readEither) import Text.Read (readEither)
@ -19,16 +19,18 @@ data ParseError
deriving (Show) deriving (Show)
data Error data Error
= ReadFileError String = ReadFileError FilePath IOError
| ParseInputError [ParseError] | ParseInputError ParseError
deriving (Show) deriving (Show)
partOne :: String -> IO (Either Error Int) partOne :: String -> IO (Either Error Int)
partOne filepath = do partOne filepath = do
result <- tryReadFile filepath result <- try (readFile filepath)
case result of case result of
Left readError -> return (Left readError) Left readError ->
Right contents -> return (calculateScore <$> parseInput contents) return $ Left (ReadFileError filepath readError)
Right contents ->
return $ calculateScore <$> BF.first ParseInputError (parseInput contents)
where where
-- To calculate the overall score we just need to -- To calculate the overall score we just need to
-- sort the lists and calculate the distances between each element -- sort the lists and calculate the distances between each element
@ -38,10 +40,10 @@ partOne filepath = do
partTwo :: String -> IO (Either Error Int) partTwo :: String -> IO (Either Error Int)
partTwo filepath = do partTwo filepath = do
result <- tryReadFile filepath result <- try (readFile filepath)
case result of case result of
Left readError -> return (Left readError) Left readError -> return $ Left (ReadFileError filepath readError)
Right contents -> return (calculateScore <$> parseInput contents) Right contents -> return $ calculateScore <$> BF.first ParseInputError (parseInput contents)
where where
-- To calculate the overall score we need to sum all the similarity scores -- To calculate the overall score we need to sum all the similarity scores
-- of each element from the left list applied to the right list -- of each element from the left list applied to the right list
@ -51,17 +53,15 @@ partTwo filepath = do
similarity id ids = id * count id ids similarity id ids = id * count id ids
count x = length . filter (== x) count x = length . filter (== x)
parseInput :: String -> Either Error ([LocationID], [LocationID]) parseInput :: String -> Either ParseError ([LocationID], [LocationID])
parseInput input = Bifunctor.first ParseInputError $ unzip <$> parseLines (lines input) parseInput xs =
case errors of
parseLines :: [String] -> Either [ParseError] [(LocationID, LocationID)] [] -> Right parsedInput
parseLines xs = (parseError : _) -> Left parseError
if null errors
then Right parsedLines
else Left errors
where where
errors = lefts $ map (parseWords . words) xs parse = map (parseWords . words) . lines
parsedLines = rights $ map (parseWords . words) xs errors = lefts $ parse xs
parsedInput = unzip $ rights $ parse xs
parseWords :: [String] -> Either ParseError (LocationID, LocationID) parseWords :: [String] -> Either ParseError (LocationID, LocationID)
parseWords [left, right] = do parseWords [left, right] = do
@ -69,24 +69,16 @@ parseWords [left, right] = do
rightLocation <- parseRight rightLocation <- parseRight
return (leftLocation, rightLocation) return (leftLocation, rightLocation)
where where
parseLeft :: Either ParseError LocationID
parseLeft = parseLeft =
Bifunctor.first BF.first
(ParseLocationError left) (ParseLocationError left)
(readEither left :: Either String LocationID) (readEither left :: Either String LocationID)
parseRight :: Either ParseError LocationID
parseRight = parseRight =
Bifunctor.first BF.first
(ParseLocationError right) (ParseLocationError right)
(readEither right :: Either String LocationID) (readEither right :: Either String LocationID)
-- If the line does not contain exactly two words, -- If the line does not contain exactly two words,
-- it is considered an error -- it is considered an error
parseWords xs' = Left $ ParseLineError (unwords xs') parseWords xs' = Left $ ParseLineError (unwords xs')
tryReadFile :: String -> IO (Either Error String)
tryReadFile filepath = Bifunctor.first (const $ ReadFileError filepath) <$> io
where
io :: IO (Either IOError String)
io = try (readFile filepath)