aboutsummaryrefslogtreecommitdiff
path: root/2020/19a.hs
diff options
context:
space:
mode:
authorAria <me@aria.rip>2023-01-02 21:58:56 +0000
committerAria <me@aria.rip>2023-01-02 21:58:56 +0000
commit5eb58ad076f2cd435b11b140820da224b60b73d5 (patch)
tree2a67939595fbf993ff04f69b9cd3f0aa20827d96 /2020/19a.hs
initial commit
Diffstat (limited to '2020/19a.hs')
-rw-r--r--2020/19a.hs158
1 files changed, 158 insertions, 0 deletions
diff --git a/2020/19a.hs b/2020/19a.hs
new file mode 100644
index 0000000..66c8838
--- /dev/null
+++ b/2020/19a.hs
@@ -0,0 +1,158 @@
+module Day19A 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;
+
+-- Finite State Machines
+
+-- Types
+-- starting states, accept states, transitions, epsilon transitions
+newtype FSM k = FSM (S.Set k, S.Set k, S.Set (k, Char, k), S.Set (k, k))
+ deriving (Show, Eq)
+
+-- Map a set of transitions
+mapTransitions :: Ord a => Ord b => (a -> b) -> S.Set (a, Char, a) -> S.Set (b, Char, b)
+mapTransitions f = S.map (\(s, c, t) -> (f s, c, f t))
+
+-- Map a set of epsilon transitions
+mapEpsilons :: Ord a => Ord b => (a -> b) -> S.Set (a, a) -> S.Set (b, b)
+mapEpsilons f = S.map (\(s, t) -> (f s, f t))
+
+-- Concatenate two FSMs
+concatFSMs :: Ord a => Ord b => FSM a -> FSM b -> FSM (Either a b)
+concatFSMs (FSM (ssa, fsa, tsa, esa)) (FSM (ssb, fsb, tsb, esb)) = FSM (S.map Left ssa, S.map Right fsb, ts', es')
+ where ts' = mapTransitions Left tsa `S.union` mapTransitions Right tsb
+ es' = mapEpsilons Left esa `S.union` mapEpsilons Right esb
+ `S.union` S.fromList [(Left s, Right d) | s <- S.toList fsa, d <- S.toList ssb]
+
+-- Take the union of two FSMs
+unionFSMs :: Ord a => Ord b => FSM a -> FSM b -> FSM (Either a b)
+unionFSMs (FSM (ssa, fsa, tsa, esa)) (FSM (ssb, fsb, tsb, esb)) = FSM (ss', fs', ts', es')
+ where ss' = S.map Left ssa `S.union` S.map Right ssb
+ fs' = S.map Left fsa `S.union` S.map Right fsb
+ ts' = mapTransitions Left tsa `S.union` mapTransitions Right tsb
+ es' = mapEpsilons Left esa `S.union` mapEpsilons Right esb
+
+-- Add a key to the given list. Used when generating lookup tables for new states
+addKey :: Eq a => Ord a => a -> [(a, Int)] -> [(a, Int)]
+addKey s [] = [(s, 1)]
+addKey s ks = ks ++ as
+ where as = case lookup s ks of
+ Just _ -> []
+ Nothing -> [(s, (snd . last) ks + 1)]
+
+-- Get all states in the given FSM
+allStates :: Ord a => FSM a -> S.Set a
+allStates (FSM (ss, fs, ts, es)) = ss `S.union` fs
+ `S.union` S.map fst3 ts `S.union` S.map lst3 ts
+ `S.union` S.map fst es `S.union` S.map snd es
+
+-- Convert a FSM of any type to an int based one.
+normalizeStates :: Eq a => Ord a => FSM a -> FSM Int
+normalizeStates fsm@(FSM (ss, fs, ts, es)) = FSM (ss', fs', ts', es')
+ where keymap = S.foldr addKey [] (allStates fsm)
+ ss' = S.map (unwrap . (`lookup` keymap)) ss
+ fs' = S.map (unwrap . (`lookup` keymap)) fs
+ ts' = mapTransitions (unwrap . (`lookup` keymap)) ts
+ es' = mapEpsilons (unwrap . (`lookup` keymap)) es
+
+-- Convert a rule to a FSM
+toFSM :: M.Map Int Rule -> Rule -> FSM Int
+toFSM m End = FSM (S.singleton 1, S.singleton 1, S.empty, S.empty)
+toFSM m (Literal x) = FSM (S.singleton 1, S.singleton 2, S.singleton (1, x, 2), S.empty)
+toFSM m (Reference n) = toFSM m r
+ where (Just r) = M.lookup n m
+toFSM m (Union a b) = normalizeStates $ unionFSMs (toFSM m a) (toFSM m b)
+toFSM m (Concat a b) = normalizeStates $ concatFSMs (toFSM m a) (toFSM m b)
+
+-- Get the new states after performing the given transition
+performTransition :: Ord a => S.Set (a, Char, a) -> S.Set a -> Char -> S.Set a
+performTransition ts ss t = S.fromList [d | (s, c, d) <- S.toList ts, c == t, s `S.member` ss]
+
+-- Get the new states including those that come from epsilon transitions
+performEpsilons :: Ord a => S.Set (a, a) -> S.Set a -> S.Set a
+performEpsilons es ss = S.fromList [d | (s, d) <- S.toList es, s `S.member` ss] `S.union` ss
+
+-- Run all given transitions from the given starting state.
+runFSM :: Ord a => FSM a -> String -> S.Set a -> S.Set a
+runFSM _ "" ss = ss
+runFSM fsm@(FSM (_, _, ts, es)) (x:xs) ss = runFSM fsm xs $ performEpsilons es $ performTransition ts ss x
+
+-- Determine if the given FSM will accept the given string
+acceptedBy :: Ord a => FSM a -> String -> Bool
+acceptedBy fsm@(FSM (ss, fs, _, _)) xs = any (`S.member` fs) $ runFSM fsm xs ss
+
+-- runghc --ghc-arg='-package split' 19a.hs inputs/day19
+main :: IO ()
+main = do
+ args <- getArgs;
+ (rs, ms) <- parseFile (head args);
+ let fsm = toFSM rs (unwrap (M.lookup 0 rs));
+
+ let ps = filter (acceptedBy fsm) ms;
+
+ printf "Answer = %d\n" (length ps) :: IO ();
+
+ 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 \ No newline at end of file