refactor: Handle file read error

This commit is contained in:
Victor Martinez 2025-03-11 23:45:43 +01:00
parent 0721ec8c1f
commit 36fbdd5864

View file

@ -4,6 +4,7 @@ module Day1
)
where
import Control.Exception (try)
import qualified Data.Bifunctor as Bifunctor
import Data.Either
import Data.List (sort)
@ -12,14 +13,22 @@ import Prelude hiding (id)
type LocationID = Int
data Error = ParseLocationError String String | ParseLineError String
data ParseError
= ParseLocationError String String
| ParseLineError String
deriving (Show)
data Error
= ReadFileError String
| ParseInputError [ParseError]
deriving (Show)
partOne :: String -> IO (Either Error Int)
partOne filepath = do
contents <- readFile filepath
let score = calculateScore <$> parseInput contents
return score
result <- tryReadFile filepath
case result of
Left readError -> return (Left readError)
Right contents -> return (calculateScore <$> parseInput contents)
where
-- To calculate the overall score we just need to
-- sort the lists and calculate the distances between each element
@ -29,9 +38,10 @@ partOne filepath = do
partTwo :: String -> IO (Either Error Int)
partTwo filepath = do
contents <- readFile filepath
let score = calculateScore <$> parseInput contents
return score
result <- tryReadFile filepath
case result of
Left readError -> return (Left readError)
Right contents -> return (calculateScore <$> parseInput contents)
where
-- 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
@ -42,31 +52,30 @@ partTwo filepath = do
count x = length . filter (== x)
parseInput :: String -> Either Error ([LocationID], [LocationID])
parseInput input = do
parsedLines <- parseLines (lines input)
return (unzip parsedLines)
parseInput input = Bifunctor.first ParseInputError $ unzip <$> parseLines (lines input)
parseLines :: [String] -> Either Error [(LocationID, LocationID)]
parseLines xs = case take 1 errors of
[parseError] -> Left parseError
_ -> Right parsedLines
parseLines :: [String] -> Either [ParseError] [(LocationID, LocationID)]
parseLines xs =
if null errors
then Right parsedLines
else Left errors
where
errors = lefts $ map (parseWords . words) xs
parsedLines = rights $ map (parseWords . words) xs
parseWords :: [String] -> Either Error (LocationID, LocationID)
parseWords :: [String] -> Either ParseError (LocationID, LocationID)
parseWords [left, right] = do
leftLocation <- parseLeft
rightLocation <- parseRight
return (leftLocation, rightLocation)
where
parseLeft :: Either Error LocationID
parseLeft :: Either ParseError LocationID
parseLeft =
Bifunctor.first
(ParseLocationError left)
(readEither left :: Either String LocationID)
parseRight :: Either Error LocationID
parseRight :: Either ParseError LocationID
parseRight =
Bifunctor.first
(ParseLocationError right)
@ -75,3 +84,9 @@ parseWords [left, right] = do
-- If the line does not contain exactly two words,
-- it is considered an error
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)