From 1d384b16006e3e82d8714f5e2b360b764e4a84ba Mon Sep 17 00:00:00 2001 From: ndaelman Date: Sat, 7 Jun 2025 16:25:18 +0200 Subject: [PATCH 1/5] First draft solution (not yet complete) - parse inputs robustly - basic logic --- 2024/day_5/rules.txt | 21 +++++++++++++++++++++ 2024/day_5/solution.hs | 35 +++++++++++++++++++++++++++++++++++ 2024/day_5/updates.txt | 6 ++++++ 3 files changed, 62 insertions(+) create mode 100644 2024/day_5/rules.txt create mode 100644 2024/day_5/solution.hs create mode 100644 2024/day_5/updates.txt diff --git a/2024/day_5/rules.txt b/2024/day_5/rules.txt new file mode 100644 index 0000000..471c7ec --- /dev/null +++ b/2024/day_5/rules.txt @@ -0,0 +1,21 @@ +47|53 +97|13 +97|61 +97|47 +75|29 +61|13 +75|53 +29|13 +97|29 +53|29 +61|53 +97|53 +61|29 +47|13 +75|47 +97|75 +47|61 +75|61 +47|29 +75|13 +53|13 diff --git a/2024/day_5/solution.hs b/2024/day_5/solution.hs new file mode 100644 index 0000000..bd7ec1e --- /dev/null +++ b/2024/day_5/solution.hs @@ -0,0 +1,35 @@ +import Data.List +import Text.Parsec +import Text.Parsec.String (Parser, parseFromFile) + +main :: IO () +main = do + updates_c <- filterEmpty <$> parseFromFile (sepEndBy1 updates newline) "updates.txt" + rules_c <- parseFromFile (sepEndBy1 rule newline) "rules.txt" + print updates_c + print rules_c + +-- assumes that each integer only appears once +check :: Integer -> Integer -> [Integer] -> Bool +check i j ks = + case (elemIndex i ks, elemIndex j ks) of + (Nothing, _) -> True + (_, Nothing) -> True + (Just x, Just y) -> x < y + +-- unsafe especially in case of even lists +middlePage :: [Integer] -> Integer +middlePage ks = ks !! div 2 (length ks) + +filterEmpty :: Either ParseError [[a]] -> Either ParseError [[a]] +filterEmpty (Right cont) = Right $ filter (not . null) cont +filterEmpty (Left err) = Left err + +updates :: Parser [Integer] +updates = sepBy digits (char ',') + +rule :: Parser (Integer, Integer) +rule = (,) <$> digits <* char '|' <*> digits + +digits :: Parser Integer +digits = read <$> many1 digit diff --git a/2024/day_5/updates.txt b/2024/day_5/updates.txt new file mode 100644 index 0000000..895fa37 --- /dev/null +++ b/2024/day_5/updates.txt @@ -0,0 +1,6 @@ +75,47,61,53,29 +97,61,53,29,13 +75,29,13 +75,97,47,61,53 +61,13,29 +97,13,75,29,47 From d355bd93bd29a12951d5a11b8800c89cea572923 Mon Sep 17 00:00:00 2001 From: ndaelman Date: Sat, 7 Jun 2025 17:24:41 +0200 Subject: [PATCH 2/5] - Extend draft - TODO: debug answer: 247 vs 143 --- 2024/day_5/solution.hs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/2024/day_5/solution.hs b/2024/day_5/solution.hs index bd7ec1e..1410950 100644 --- a/2024/day_5/solution.hs +++ b/2024/day_5/solution.hs @@ -4,18 +4,21 @@ import Text.Parsec.String (Parser, parseFromFile) main :: IO () main = do - updates_c <- filterEmpty <$> parseFromFile (sepEndBy1 updates newline) "updates.txt" rules_c <- parseFromFile (sepEndBy1 rule newline) "rules.txt" - print updates_c - print rules_c + updates_c <- filterEmpty <$> parseFromFile (sepEndBy1 updates newline) "updates.txt" + case (rules_c, updates_c) of + (Right rules_d, Right updates_d) -> print $ logic rules_d updates_d + _ -> print "Invalid input format" + +logic :: [(Integer, Integer)] -> [[Integer]] -> Integer +logic rules updates = sum $ map middlePage (filter (\update -> all (\rule -> uncurry check rule update) rules) updates) -- assumes that each integer only appears once check :: Integer -> Integer -> [Integer] -> Bool check i j ks = case (elemIndex i ks, elemIndex j ks) of - (Nothing, _) -> True - (_, Nothing) -> True (Just x, Just y) -> x < y + _ -> True -- unsafe especially in case of even lists middlePage :: [Integer] -> Integer From 1076fa53b91e03632fbee347785e6af52de335f3 Mon Sep 17 00:00:00 2001 From: ndaelman Date: Sun, 8 Jun 2025 18:07:03 +0200 Subject: [PATCH 3/5] - Fix logic bug (143) - Raise errors when business logic `middlePage` fails --- 2024/day_5/solution.hs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/2024/day_5/solution.hs b/2024/day_5/solution.hs index 1410950..bd773a9 100644 --- a/2024/day_5/solution.hs +++ b/2024/day_5/solution.hs @@ -11,7 +11,10 @@ main = do _ -> print "Invalid input format" logic :: [(Integer, Integer)] -> [[Integer]] -> Integer -logic rules updates = sum $ map middlePage (filter (\update -> all (\rule -> uncurry check rule update) rules) updates) +logic rules updates = sum $ map middlePage (checkAll rules updates) + +checkAll :: [(Integer, Integer)] -> [[Integer]] -> [[Integer]] +checkAll rules = filter (\update -> all (\rule -> uncurry check rule update) rules) -- assumes that each integer only appears once check :: Integer -> Integer -> [Integer] -> Bool @@ -22,7 +25,10 @@ check i j ks = -- unsafe especially in case of even lists middlePage :: [Integer] -> Integer -middlePage ks = ks !! div 2 (length ks) +middlePage [] = error "middlePage: cannot find middle of empty list" +middlePage ks + | even (length ks) = error "middlePage: list has even length, no single middle element" + | otherwise = ks !! div (length ks) 2 filterEmpty :: Either ParseError [[a]] -> Either ParseError [[a]] filterEmpty (Right cont) = Right $ filter (not . null) cont From 20671eedf4ce89f3e458c7fb56c6f2086e0be868 Mon Sep 17 00:00:00 2001 From: ndaelman Date: Sun, 8 Jun 2025 18:36:43 +0200 Subject: [PATCH 4/5] Manage edge cases business logic and --- 2024/day_5/solution.hs | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/2024/day_5/solution.hs b/2024/day_5/solution.hs index bd773a9..8c80af5 100644 --- a/2024/day_5/solution.hs +++ b/2024/day_5/solution.hs @@ -1,14 +1,18 @@ import Data.List +import qualified Data.Set as Set import Text.Parsec import Text.Parsec.String (Parser, parseFromFile) main :: IO () main = do - rules_c <- parseFromFile (sepEndBy1 rule newline) "rules.txt" - updates_c <- filterEmpty <$> parseFromFile (sepEndBy1 updates newline) "updates.txt" + rules_c <- parseFromFile (sepEndBy1 rule newline) ri + updates_c <- filterEmpty <$> parseFromFile (sepEndBy1 updates newline) ui case (rules_c, updates_c) of (Right rules_d, Right updates_d) -> print $ logic rules_d updates_d - _ -> print "Invalid input format" + (Left _, Left _) -> error $ "Invalid input formats in " ++ show ri ++ " and " ++ show ui + (Left _, _) -> error $ "Invalid input format in " ++ show ri + (_, Left _) -> error $ "Invalid input format in " ++ show ui + where ri = "rules.txt"; ui = "updates.txt" logic :: [(Integer, Integer)] -> [[Integer]] -> Integer logic rules updates = sum $ map middlePage (checkAll rules updates) @@ -18,17 +22,20 @@ checkAll rules = filter (\update -> all (\rule -> uncurry check rule update) rul -- assumes that each integer only appears once check :: Integer -> Integer -> [Integer] -> Bool +check _ _ [] = error "check: cannot check rules of empty list" check i j ks = - case (elemIndex i ks, elemIndex j ks) of - (Just x, Just y) -> x < y - _ -> True + if length ks == Set.size (Set.fromList ks) + then case (elemIndex i ks, elemIndex j ks) of + (Just x, Just y) -> x < y + _ -> True + else error $ "check: requires different pages in " ++ show ks --- unsafe especially in case of even lists middlePage :: [Integer] -> Integer middlePage [] = error "middlePage: cannot find middle of empty list" middlePage ks - | even (length ks) = error "middlePage: list has even length, no single middle element" - | otherwise = ks !! div (length ks) 2 + | even l = error $ "middlePage: updates " ++ show ks ++ " has even length" + | otherwise = ks !! div l 2 + where l = length ks filterEmpty :: Either ParseError [[a]] -> Either ParseError [[a]] filterEmpty (Right cont) = Right $ filter (not . null) cont From c2be3966e3fc4376092ae2008aa61e66d19929cf Mon Sep 17 00:00:00 2001 From: ndaelman Date: Sun, 8 Jun 2025 19:24:38 +0200 Subject: [PATCH 5/5] - Restructure project - Add first test --- 2024/day_5/Main.hs | 29 ++++++++++++++++++++ 2024/day_5/{solution.hs => Solution.hs} | 31 ++++++--------------- 2024/day_5/advent-day5.cabal | 36 +++++++++++++++++++++++++ 2024/day_5/test.hs | 13 +++++++++ 4 files changed, 86 insertions(+), 23 deletions(-) create mode 100644 2024/day_5/Main.hs rename 2024/day_5/{solution.hs => Solution.hs} (51%) create mode 100644 2024/day_5/advent-day5.cabal create mode 100644 2024/day_5/test.hs diff --git a/2024/day_5/Main.hs b/2024/day_5/Main.hs new file mode 100644 index 0000000..8e47009 --- /dev/null +++ b/2024/day_5/Main.hs @@ -0,0 +1,29 @@ +import Solution +import Text.Parsec +import Text.Parsec.String (Parser, parseFromFile) + +main :: IO () +main = do + rules_c <- parseFromFile (sepEndBy1 rule newline) ri + updates_c <- filterEmpty <$> parseFromFile (sepEndBy1 updates newline) ui + case (rules_c, updates_c) of + (Right rules_d, Right updates_d) -> print $ logic rules_d updates_d + (Left _, Left _) -> error $ "Invalid input formats in " ++ show ri ++ " and " ++ show ui + (Left _, _) -> error $ "Invalid input format in " ++ show ri + (_, Left _) -> error $ "Invalid input format in " ++ show ui + where ri = "rules.txt"; ui = "updates.txt" + +filterEmpty :: Either ParseError [[a]] -> Either ParseError [[a]] +filterEmpty (Right cont) = Right $ filter (not . null) cont +filterEmpty (Left err) = Left err + +-- parsing + +updates :: Parser [Integer] +updates = sepBy digits (char ',') + +rule :: Parser (Integer, Integer) +rule = (,) <$> digits <* char '|' <*> digits + +digits :: Parser Integer +digits = read <$> many1 digit \ No newline at end of file diff --git a/2024/day_5/solution.hs b/2024/day_5/Solution.hs similarity index 51% rename from 2024/day_5/solution.hs rename to 2024/day_5/Solution.hs index 8c80af5..8a7822c 100644 --- a/2024/day_5/solution.hs +++ b/2024/day_5/Solution.hs @@ -1,18 +1,15 @@ +module Solution + ( check + , middlePage + , logic + , checkAll + ) where + import Data.List import qualified Data.Set as Set import Text.Parsec import Text.Parsec.String (Parser, parseFromFile) -main :: IO () -main = do - rules_c <- parseFromFile (sepEndBy1 rule newline) ri - updates_c <- filterEmpty <$> parseFromFile (sepEndBy1 updates newline) ui - case (rules_c, updates_c) of - (Right rules_d, Right updates_d) -> print $ logic rules_d updates_d - (Left _, Left _) -> error $ "Invalid input formats in " ++ show ri ++ " and " ++ show ui - (Left _, _) -> error $ "Invalid input format in " ++ show ri - (_, Left _) -> error $ "Invalid input format in " ++ show ui - where ri = "rules.txt"; ui = "updates.txt" logic :: [(Integer, Integer)] -> [[Integer]] -> Integer logic rules updates = sum $ map middlePage (checkAll rules updates) @@ -20,7 +17,6 @@ logic rules updates = sum $ map middlePage (checkAll rules updates) checkAll :: [(Integer, Integer)] -> [[Integer]] -> [[Integer]] checkAll rules = filter (\update -> all (\rule -> uncurry check rule update) rules) --- assumes that each integer only appears once check :: Integer -> Integer -> [Integer] -> Bool check _ _ [] = error "check: cannot check rules of empty list" check i j ks = @@ -37,15 +33,4 @@ middlePage ks | otherwise = ks !! div l 2 where l = length ks -filterEmpty :: Either ParseError [[a]] -> Either ParseError [[a]] -filterEmpty (Right cont) = Right $ filter (not . null) cont -filterEmpty (Left err) = Left err - -updates :: Parser [Integer] -updates = sepBy digits (char ',') - -rule :: Parser (Integer, Integer) -rule = (,) <$> digits <* char '|' <*> digits - -digits :: Parser Integer -digits = read <$> many1 digit + diff --git a/2024/day_5/advent-day5.cabal b/2024/day_5/advent-day5.cabal new file mode 100644 index 0000000..1eb3d21 --- /dev/null +++ b/2024/day_5/advent-day5.cabal @@ -0,0 +1,36 @@ +cabal-version: 2.4 +name: advent-day5 +version: 0.1.0.0 + +library + exposed-modules: Solution + hs-source-dirs: . + build-depends: + base, + parsec, + containers + default-language: Haskell2010 + +executable solution + main-is: Main.hs + hs-source-dirs: . + other-modules: Solution + build-depends: + base, + advent-day5, + parsec, + containers + default-language: Haskell2010 + +test-suite tests + type: exitcode-stdio-1.0 + main-is: test.hs + hs-source-dirs: . + other-modules: Solution + build-depends: + base, + advent-day5, + QuickCheck, + parsec, + containers + default-language: Haskell2010 \ No newline at end of file diff --git a/2024/day_5/test.hs b/2024/day_5/test.hs new file mode 100644 index 0000000..0cc1367 --- /dev/null +++ b/2024/day_5/test.hs @@ -0,0 +1,13 @@ +import Test.QuickCheck +import Solution (check, middlePage) +import Data.List (nub) + +main :: IO () +main = do + quickCheck reverseCheck + +reverseCheck :: Integer -> Integer -> [Integer] -> Property +reverseCheck i j ks = + let uniqueKs = nub ks + in length uniqueKs > 1 && i `elem` uniqueKs && j `elem` uniqueKs && i /= j ==> + check i j uniqueKs /= check i j (reverse uniqueKs)