-- a haiku: -- this is some bad code -- but what more can you expect -- from a bad problem? import Data.Char (isDigit, isHexDigit) import Data.List (sort, nub) main :: IO () main = do raw <- readFile "day4.txt" let ls = map (concat . (map words)) $ split "" $ lines raw ansA = length $ filter (== True) $ map solveA ls ansB = length $ filter (== True) $ map solveB ls in do putStrLn $ "day4a: " ++ (show ansA) putStrLn $ "day4b: " ++ (show ansB) rtags :: [String] rtags = ["byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"] extractTag :: String -> String extractTag [] = error "empty tag" extractTag s = takeWhile (/= ':') s extractValue :: String -> String extractValue [] = error "empty value" extractValue s = drop 1 $ dropWhile (/= ':') s applyAll :: [a] -> [(a -> b)] -> [b] applyAll [] [] = [] applyAll (a:as) (f:fs) = if length as /= length fs then error "applyAll: lists must be same length" else (f a) : (applyAll as fs) solveA :: [String] -> Bool solveA [] = False solveA s = foldr (&&) True $ map (`elem` tags) rtags where tags = map extractTag s solveB :: [String] -> Bool solveB [] = False solveB s = solveA s && (foldr (&&) True $ applyAll ls [ byrValid , eclValid , eyrValid , hclValid , hgtValid , iyrValid , pidValid ]) where ls = (sort . nub) $ filter (\x -> elem (extractTag x) rtags) s yearValid :: Int -> Int -> String -> Bool yearValid l u s' = (length s) == 4 && (read s) >= l && (read s) <= u where s = drop 1 $ dropWhile (/= ':') s' byrValid :: String -> Bool byrValid = yearValid 1920 2002 iyrValid :: String -> Bool iyrValid = yearValid 2010 2020 eyrValid :: String -> Bool eyrValid = yearValid 2020 2030 eclValid :: String -> Bool eclValid s' = s `elem` ["amb","blu","brn","gry","grn","hzl","oth"] where s = drop 1 $ dropWhile (/= ':') s' pidValid :: String -> Bool pidValid s' = (length s) == 9 && all isDigit s where s = drop 1 $ dropWhile (/= ':') s' hclValid :: String -> Bool hclValid s' = h == '#' && all isHexDigit s where (h:s) = drop 1 $ dropWhile (/= ':') s' hgtValid :: String -> Bool hgtValid s' = case unit of "cm" -> val >= 150 && val <= 193 "in" -> val >= 59 && val <= 76 _ -> False where s = drop 1 $ dropWhile (/= ':') s' (val,unit) = head $ (reads s :: [(Int,String)]) split :: Eq a => a -> [a] -> [[a]] split _ [] = [] split d as = chunk : (split d rest) where chunk = takeWhile (/= d) as rest = drop 1 $ dropWhile (/= d) as