From 36fbdd586431825f2879f4c65156a75d67f080d4 Mon Sep 17 00:00:00 2001 From: Victor Martinez <49537445+JasterV@users.noreply.github.com> Date: Tue, 11 Mar 2025 23:45:43 +0100 Subject: [PATCH] refactor: Handle file read error --- src/Day1.hs | 49 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/src/Day1.hs b/src/Day1.hs index 5b011b1..f061282 100644 --- a/src/Day1.hs +++ b/src/Day1.hs @@ -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)