aboutsummaryrefslogtreecommitdiff
path: root/2020/19b.hs
blob: d1fa763011ada1533b950c4a43713ddd81cd0dce (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
module Day19B where

import System.Environment (getArgs)
import Text.Printf
import Data.List.Split (splitOn)
import Data.Char (isDigit)
import qualified Data.Set as S
import qualified Data.Map.Strict as M


-- Rules parser

-- Types
data Rule = Literal Char | Reference Int | Concat Rule Rule | Union Rule Rule | End
        deriving (Eq, Show);

-- Convert a list of rules to the union of those rules
toUnion :: [Rule] -> Rule
toUnion [x] = x
toUnion (x:ys) = Union x (toUnion ys)

-- Parse the given line seperated list of rules
-- Returns a map from id to rule
parseRules :: String -> M.Map Int Rule
parseRules = M.fromList . map parseRule . lines

-- Parse the given rule, in format `0: 1 2 | 3 4`
parseRule :: String -> (Int, Rule)
parseRule s = (read idx, toUnion $ map parseNoUnion $ splitOn " | " def)
            where [idx, def] = splitOn ": " s


-- Parse the lower precedence parts (anything that isn't a union)
parseNoUnion :: String -> Rule
parseNoUnion "" = End;
parseNoUnion (' ':xs) = parseNoUnion xs
parseNoUnion ('"':xs) = Literal (head xs)
parseNoUnion xs@(x:_) | not (null re)= Concat (Reference (read n)) (parseNoUnion (drop (length n + 1) xs))
                      | otherwise = Reference (read n)
                        where (n:re) = splitOn " " xs

-- Parse the input into a list of rules and list of messages
parseInput :: String -> (M.Map Int Rule, [String])
parseInput s = (parseRules rs, lines ms)
        where [rs, ms] = splitOn "\n\n" s

-- Parse the file with the given name
parseFile :: String -> IO (M.Map Int Rule, [String])
parseFile f = do 
                contents <- readFile f
                return $ parseInput contents;

-- Parser

-- Returns remaining if valid
attemptConsume :: M.Map Int Rule -> Rule -> String -> [Maybe String]
attemptConsume m End xs = [Just xs]
attemptConsume m r [] = [Nothing]
attemptConsume m (Literal t) (x:xs) | t == x = [Just xs]
                                    | otherwise = [Nothing]
attemptConsume m (Concat a b) xs = concat [attemptConsume m b re | Just re <- attemptConsume m a xs]
attemptConsume m (Union a b) xs = attemptConsume m b xs ++ attemptConsume m a xs
attemptConsume m (Reference n) xs = attemptConsume m (unwrap $ M.lookup n m) xs


matchedBy :: M.Map Int Rule -> String -> Bool
matchedBy m s = or [x == "" | Just x <- ps]
              where ps = attemptConsume m (unwrap $ M.lookup 0 m) s

-- runghc --ghc-arg='-package split' 19b.hs inputs/day19
main :: IO ()
main = do 
        args <- getArgs;
        (rs, ms) <- parseFile (head args);

        let ps = filter (matchedBy rs) ms;

        print (length ps);

        return ();

-- Helpers

-- Unwrap a maybe value, throwing an error if wrong
unwrap :: Maybe a -> a
unwrap (Just x) = x
unwrap _ = error "unwrap on null value"

-- First of a 3tuple
fst3 :: (a, b, c) -> a
fst3 (a, _, _) = a

-- Last of a 3 tuple
lst3 :: (a, b, c) -> c
lst3 (_, _, c) = c