aoc2024

My solutions to the 2024 Advent of Code puzzles
git clone git://git.ethandl.dev/aoc2024
Log | Files | Refs | LICENSE

Day2.hs (3627B)


      1 module Day2 (solution) where
      2 
      3 import Control.Monad (join, liftM)
      4 import Data.List (genericLength)
      5 import Utils (SolType(..), safeRead, integerDiff, xor, (|||))
      6 
      7 input :: IO String
      8 input = readFile "inputs/day2"
      9 
     10 exampleInput :: String
     11 exampleInput = join [
     12     "7 6 4 2 1\n",
     13     "1 2 7 8 9\n",
     14     "9 7 6 2 1\n",
     15     "1 3 2 4 5\n",
     16     "8 6 4 4 1\n",
     17     "1 3 6 7 9\n"
     18   ]
     19 
     20 type Level = Integer
     21 
     22 -- | Parse the input into a list of level lists
     23 -- Example:
     24 -- >>> parse exampleInput
     25 -- Just [[7,6,4,2,1],[1,2,7,8,9],[9,7,6,2,1],[1,3,2,4,5],[8,6,4,4,1],[1,3,6,7,9]]
     26 -- >>> parse "this is an invalid string"
     27 -- Nothing
     28 parse :: String -> Maybe [[Level]]
     29 parse str = mapM ((mapM safeRead) . words) $ lines str
     30 
     31 -- | Determine whether a sequence is safe or not according to the instructions
     32 --   of part 1
     33 -- Example:
     34 -- >>> safeSequence [7,6,4,2,1]
     35 -- True
     36 -- >>> safeSequence [1,2,7,8,9]
     37 -- False
     38 -- >>> safeSequence [9,7,6,2,1]
     39 -- False
     40 -- >>> safeSequence [1,3,2,4,5]
     41 -- False
     42 -- >>> safeSequence [8,6,4,4,1]
     43 -- False
     44 -- >>> safeSequence [1,3,6,7,9]
     45 -- True
     46 safeSequence :: [Level] -> Bool
     47 safeSequence = helper Nothing Nothing
     48   where
     49     -- This could almost definitely be done as a fold operation but I don't care
     50     helper :: Maybe Bool -> Maybe Integer -> [Level] -> Bool
     51     helper Nothing Nothing (x:y:ys)
     52       | x == y    = False
     53       | otherwise = helper (Just $ x < y) (Just x) (y:ys)
     54     helper Nothing _ _ = False
     55     helper _ Nothing _ = False
     56     helper (Just pos) (Just x) (y:ys) = (not $ xor pos $ x < y)
     57       && diff <= 3 && diff > 0
     58       && helper (Just pos) (Just y) ys
     59       where diff = integerDiff x y
     60     helper _ _ [] = True
     61 
     62 -- | Solution to day 2 part 1
     63 -- Example
     64 -- >>> liftM solution1 $ parse exampleInput
     65 -- Just 2
     66 solution1 :: [[Level]] -> Integer
     67 solution1 = genericLength . filter safeSequence
     68 
     69 -- | Finds all subsequences of the given with one less element
     70 -- Example:
     71 -- >>> reverse $ subSeqs [7,6,4,2,1]
     72 -- [[6,4,2,1],[7,4,2,1],[7,6,2,1],[7,6,4,1],[7,6,4,2]]
     73 -- >>> reverse $ subSeqs [1,2,7,8,9]
     74 -- [[2,7,8,9],[1,7,8,9],[1,2,8,9],[1,2,7,9],[1,2,7,8]]
     75 -- >>> reverse $ subSeqs [9,7,6,2,1]
     76 -- [[7,6,2,1],[9,6,2,1],[9,7,2,1],[9,7,6,1],[9,7,6,2]]
     77 -- >>> reverse $ subSeqs [1,3,2,4,5]
     78 -- [[3,2,4,5],[1,2,4,5],[1,3,4,5],[1,3,2,5],[1,3,2,4]]
     79 -- >>> reverse $ subSeqs [8,6,4,4,1]
     80 -- [[6,4,4,1],[8,4,4,1],[8,6,4,1],[8,6,4,1],[8,6,4,4]]
     81 -- >>> reverse $ subSeqs [1,3,6,7,9]
     82 -- [[3,6,7,9],[1,6,7,9],[1,3,7,9],[1,3,6,9],[1,3,6,7]]
     83 subSeqs :: [Level] -> [[Level]]
     84 subSeqs = helper [] []
     85   where
     86     helper :: [[Level]] -> [Level] -> [Level] -> [[Level]]
     87     helper acc prev (x:xs) = helper (subList : acc) (x : prev) xs
     88       where subList = reverse prev ++ xs
     89     helper acc _ [] = acc
     90 
     91 -- Brute forcing this out of laziness
     92 -- | Determine whether a sequence is safe or not with dampening
     93 -- Example:
     94 -- >>> safeDampedSequence [7,6,4,2,1]
     95 -- True
     96 -- >>> safeDampedSequence [1,2,7,8,9]
     97 -- False
     98 -- >>> safeDampedSequence [9,7,6,2,1]
     99 -- False
    100 -- >>> safeDampedSequence [1,3,2,4,5]
    101 -- True
    102 -- >>> safeDampedSequence [8,6,4,4,1]
    103 -- True
    104 -- >>> safeDampedSequence [1,3,6,7,9]
    105 -- True
    106 safeDampedSequence :: [Level] -> Bool
    107 safeDampedSequence = safeSequence ||| ((any safeSequence) . subSeqs)
    108 
    109 -- | Solution to day 2 part 2
    110 -- Example
    111 -- >>> liftM solution2 $ parse exampleInput
    112 -- Just 4
    113 solution2 :: [[Level]] -> Integer
    114 solution2 = genericLength . filter safeDampedSequence
    115 
    116 solution :: IO (SolType, SolType)
    117 solution = do
    118   parsedLists <- liftM parse input
    119   let sol1 = liftM (IntSol . solution1) parsedLists
    120   let sol2 = liftM (IntSol . solution2) parsedLists
    121   return (MaybeSol sol1, MaybeSol sol2)