-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDay19.hs
166 lines (136 loc) · 5.51 KB
/
Day19.hs
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
module Day19
( part1
, part2
) where
import Control.Monad (unless, void)
import Data.Bifunctor (bimap)
import Data.Bits (countLeadingZeros, finiteBitSize,
popCount)
import Data.ByteString (ByteString)
import Data.ByteString.UTF8 (fromString)
import Data.Char (isAlpha, isLowerCase)
import Data.HashMap.Strict (HashMap, insertWith, member, (!))
import qualified Data.HashMap.Strict as M (insert)
import Data.HashSet (HashSet)
import qualified Data.HashSet as S (insert, size)
import Data.List (partition)
import Data.Maybe (fromJust, isNothing)
import Data.Sequence (Seq ((:<|), (:|>)), fromList, (><))
import qualified Data.Sequence as Sq (null)
import Data.Text (Text, pack)
import FlatParse.Basic (char, failed, isLatinLetter, many,
optional, optional_, runParser,
satisfy, skip, some, string, switch,
takeLine, (<|>))
import Helpers.Parsers.FlatParse (extract, extractS)
import qualified Helpers.Parsers.FlatParse as F (Parser, ParserS)
type Parser = F.Parser (Formulas, Molecule)
type Parser' = F.Parser [WordAtom]
type Formulas = HashMap Atom [Molecule]
type Molecule = Seq Atom
type Atom = Text
type Back = HashMap (Int, Atom, Atom)
-- Atoms that do not transform once they are reached are C (which has to be at
-- the beginning of a word), Rn, Ar and Y. If Rn is present, then Ar is always
-- present, and if Y is present, then it is always between Rn and Ar. Rn, Y and
-- Ar are always separated by exactly one atom. From this,
-- we can build a parser.
-- Once we have our successful atomparsing, we can count the steps of reduction
-- without actually bothering about the actual path : it takes one step to
-- reduce two continuous to one or a C(RnAr) molecule to one or a 1 + RnAr
-- molecule to one, so we actually only care about the length of the
-- continuous streams of molecules and are not going to need the dictionary at
-- all for part 2, we can directly parse our molecule sequence.
data WordAtom
= RnAr [[WordAtom]]
| Continuous Int
deriving (Show)
type AtomParser = F.Parser WordAtom
separators = [pack "Rn", pack "Ar", pack "Y"]
isContinuous :: WordAtom -> Bool
isContinuous (Continuous _) = True
isContinuous _ = False
parseInput :: Parser
parseInput =
parseFormulas >>= \x ->
many parseAtom <* $(char '\n') >>= \y -> pure (x, fromList y)
parseInput' :: Parser'
parseInput' = parseFormulas' >> some atomParser <* $(char '\n')
parseFormulas :: F.Parser Formulas
parseFormulas = ($(char '\n') >> pure mempty) <|> parseLine
parseFormulas' :: F.Parser ()
parseFormulas' = void $(char '\n') <|> (void takeLine >> parseFormulas')
parseLine :: F.Parser Formulas
parseLine = do
from <- parseAtom
$(string " => ")
to <- fromList <$> some parseAtom
$(char '\n')
insertWith (++) from [to] <$> parseFormulas
parseAtom :: F.Parser Atom
parseAtom =
satisfy isAlpha >>= \x ->
optional (satisfy isLowerCase) >>= \y ->
if isNothing y
then pure $ pack [x]
else pure $ pack [x, fromJust y]
multifold :: Formulas -> Molecule -> Molecule -> HashSet Molecule
multifold formulas seen toSee
| Sq.null toSee = mempty
| m `member` formulas =
foldr (S.insert . molecule) (multifold formulas seen' toSee') (formulas ! m)
| otherwise = multifold formulas seen' toSee'
where
(m :<| toSee') = toSee
seen' = seen :|> m
molecule k = seen >< k >< toSee'
atomParser :: AtomParser
atomParser =
$(switch
[|case _ of
"Rn" -> RnAr <$> rnArParser
"\n" -> failed
_ -> Continuous . length <$> some parseNonSepAtom|])
parseNonSepAtom :: F.Parser Atom
parseNonSepAtom = do
atom <- parseAtom
if atom `elem` separators
then failed
else pure atom
rnArParser :: F.Parser [[WordAtom]]
rnArParser =
some (some atomParser >>= \x -> optional_ parseY >> pure x) >>= \x ->
parseAr >> pure x
parseY :: F.Parser ()
parseY = parseAtom >>= \a -> unless (a == pack "Y") failed
parseAr :: F.Parser ()
parseAr = parseAtom >>= \a -> unless (a == pack "Ar") failed
reduceAll :: [WordAtom] -> Int
reduceAll molecules =
length (filter isContinuous molecules) + reduce molecules - 1
reduce :: [WordAtom] -> Int
reduce =
uncurry (+)
. bimap reduceContinuous (sum . map reduceRnAr)
. partition isContinuous
reduceRnAr :: WordAtom -> Int
reduceRnAr (RnAr molecules) = (+ 1) . sum . map reduce $ molecules
-- it takes 0 steps to reduce a 1 atom molecule to 1 atom. Let's assume it takes (n-1)
-- steps to reduce an n atoms molecule to 1 atom. Then it takes (n + n - 1) = 2n
-- -1 steps to reduce a 2n atoms molecule to 1 atom, and (1 + reduce (2m)) = (1 + 2n - 1) = 2n steps to
-- reduce a (2n + 1) atoms molecule to 1 atom. QED
reduceContinuous :: [WordAtom] -> Int
reduceContinuous = (+ negate 1) . sum . map fromContinuous
where
fromContinuous (Continuous atoms) = atoms
part1 :: Bool -> ByteString -> String
part1 _ =
show
. S.size
. (\(a, b) -> multifold a mempty b)
. extract
. runParser parseInput
part2 :: Bool -> ByteString -> String
part2 _ = show . reduce . extract . runParser parseInput'