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 where
import Control.Exception (try)
import qualified Data.Bifunctor as Bifunctor import qualified Data.Bifunctor as Bifunctor
import Data.Either import Data.Either
import Data.List (sort) import Data.List (sort)
@ -12,14 +13,22 @@ import Prelude hiding (id)
type LocationID = Int 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) deriving (Show)
partOne :: String -> IO (Either Error Int) partOne :: String -> IO (Either Error Int)
partOne filepath = do partOne filepath = do
contents <- readFile filepath result <- tryReadFile filepath
let score = calculateScore <$> parseInput contents case result of
return score Left readError -> return (Left readError)
Right contents -> return (calculateScore <$> 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
@ -29,9 +38,10 @@ partOne filepath = do
partTwo :: String -> IO (Either Error Int) partTwo :: String -> IO (Either Error Int)
partTwo filepath = do partTwo filepath = do
contents <- readFile filepath result <- tryReadFile filepath
let score = calculateScore <$> parseInput contents case result of
return score Left readError -> return (Left readError)
Right contents -> return (calculateScore <$> 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
@ -42,31 +52,30 @@ partTwo filepath = do
count x = length . filter (== x) count x = length . filter (== x)
parseInput :: String -> Either Error ([LocationID], [LocationID]) parseInput :: String -> Either Error ([LocationID], [LocationID])
parseInput input = do parseInput input = Bifunctor.first ParseInputError $ unzip <$> parseLines (lines input)
parsedLines <- parseLines (lines input)
return (unzip parsedLines)
parseLines :: [String] -> Either Error [(LocationID, LocationID)] parseLines :: [String] -> Either [ParseError] [(LocationID, LocationID)]
parseLines xs = case take 1 errors of parseLines xs =
[parseError] -> Left parseError if null errors
_ -> Right parsedLines then Right parsedLines
else Left errors
where where
errors = lefts $ map (parseWords . words) xs errors = lefts $ map (parseWords . words) xs
parsedLines = rights $ 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 parseWords [left, right] = do
leftLocation <- parseLeft leftLocation <- parseLeft
rightLocation <- parseRight rightLocation <- parseRight
return (leftLocation, rightLocation) return (leftLocation, rightLocation)
where where
parseLeft :: Either Error LocationID parseLeft :: Either ParseError LocationID
parseLeft = parseLeft =
Bifunctor.first Bifunctor.first
(ParseLocationError left) (ParseLocationError left)
(readEither left :: Either String LocationID) (readEither left :: Either String LocationID)
parseRight :: Either Error LocationID parseRight :: Either ParseError LocationID
parseRight = parseRight =
Bifunctor.first Bifunctor.first
(ParseLocationError right) (ParseLocationError right)
@ -75,3 +84,9 @@ parseWords [left, right] = do
-- 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)