-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathDay14.hs
176 lines (151 loc) · 4.59 KB
/
Day14.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
167
168
169
170
171
172
173
174
175
176
#!/usr/bin/env stack
{- stack --resolver=lts-13.24 script
--package=megaparsec,parser-combinators,containers,mtl -}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NamedFieldPuns #-}
import Control.Monad.Reader
import Control.Monad.State.Strict
import Data.List.NonEmpty (NonEmpty(..))
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.String
import Parsing hiding (count)
newtype Reagent = Reagent String deriving (Show, Eq, Ord, IsString)
data Part = Int :* Reagent deriving (Show, Eq)
data Reaction = (NonEmpty Part) :=> Part deriving (Show, Eq)
-- 10 :* "LEAD" :| [1 :* "PHSTONE"] :=> 1 :* "GOLD"
infixr 6 :*
infixr 4 :=>
type CookBook = Map Reagent Reaction
type Pool = Map Reagent Int
data Lab = Lab
{ labPool :: !Pool
, labSource :: !Pool
} deriving (Show)
type MonadLab m =
( MonadReader CookBook m
, MonadState Lab m
)
main :: IO ()
main = do
checkExample exampleCB1 165
checkExample exampleCB2 13312
interact $ \s ->
let
Right rs = parseLines reactionP s
cb = buildCookBook rs
corePer1Fuel = oreForFuel cb 1
minFuelFrom1T = oneT `div` corePer1Fuel
fuelFrom1T = binSearch cb minFuelFrom1T (2 * minFuelFrom1T)
in unlines
[ "Step 1"
, show corePer1Fuel
, "Step 2"
, show fuelFrom1T
]
where
oneT = 1000000000000
oreForFuel cb n = count labSource "ORE" $ produce (n :* "FUEL") cb cleanLab
binSearch cb l h
| l == m = l
| x > oneT = binSearch cb l m
| otherwise = binSearch cb m h
where
m = l + (h - l) `div` 2
x = oreForFuel cb m
-- parsing
partP :: Parser Part
partP = (:*) <$> decimal <*> (space1 *> reagentP)
where
reagentP = Reagent <$> some (satisfy isUpper)
reactionP :: Parser Reaction
reactionP = (:=>)
<$> partP `sepBy1` (char ',' <* space1)
<*> (space1 *> string "=>" *> space1 *> partP)
-- reacting
cleanLab :: Lab
cleanLab = Lab Map.empty Map.empty
buildCookBook :: [Reaction] -> CookBook
buildCookBook =
Map.fromListWithKey nonDup . map ((,) <$> resultName <*> id)
where
nonDup (Reagent k) _ _ = error $ "Two reactions for: " ++ k
resultName (_ :=> _ :* n) = n
produce :: Part -> CookBook -> Lab -> Lab
produce p cb lab =
makeSome p `runReaderT` cb `execState` lab
makeSome :: MonadLab m => Part -> m ()
makeSome (n :* tr) =
asks (Map.lookup tr) >>= \case
Just r@(_ :=> m :* _) -> perform $ (n `from` m) `times` r
Nothing ->
modify $ \lab@Lab{labSource, labPool} -> lab
{ labPool = record labPool
, labSource = record labSource
}
where
record = Map.insertWith (+) tr n
use :: MonadLab m => Part -> m ()
use p@(n :* tr) = do
gets (Map.lookup tr . labPool) >>= \case
Just m | m >= n -> pure ()
| otherwise -> makeSome $ (n - m) :* tr
_ -> makeSome p
modify $ \lab@Lab{labPool} -> lab
{ labPool = Map.adjust (subtract n) tr labPool
}
perform :: MonadLab m => Reaction -> m ()
perform (r :| rs :=> n :* tr) = do
mapM_ use (r:rs)
modify $ \l@(Lab {labPool}) -> l
{ labPool = Map.insertWith (+) tr n labPool
}
-- helpers
from :: Int -> Int -> Int
from x y
| x <= y = 1
| otherwise = k + (if r > 0 then 1 else 0)
where
(k, r) = divMod x y
count :: (Lab -> Pool) -> Reagent -> Lab -> Int
count l r = Map.findWithDefault 0 r . l
times :: Int -> Reaction -> Reaction
times n (r :| rs :=> tr) = f r :| map f rs :=> f tr
where
f (x :* y) = (x * n) :* y
-- example
exampleCB1, exampleCB2 :: CookBook
(exampleCB1:exampleCB2:[]) = map justParse
[ [ "9 ORE => 2 A"
, "8 ORE => 3 B"
, "7 ORE => 5 C"
, "3 A, 4 B => 1 AB"
, "5 B, 7 C => 1 BC"
, "4 C, 1 A => 1 CA"
, "2 AB, 3 BC, 4 CA => 1 FUEL"
]
, [ "157 ORE => 5 NZVS"
, "165 ORE => 6 DCFZ"
, "44 XJWVT, 5 KHKGT, 1 QDVJ, 29 NZVS, 9 GPVTF, 48 HKGWZ => 1 FUEL"
, "12 HKGWZ, 1 GPVTF, 8 PSHF => 9 QDVJ"
, "179 ORE => 7 PSHF"
, "177 ORE => 5 HKGWZ"
, "7 DCFZ, 7 PSHF => 2 XJWVT"
, "165 ORE => 2 GPVTF"
, "3 DCFZ, 7 NZVS, 5 HKGWZ, 10 PSHF => 8 KHKGT"
]
]
where
justParse = either (error "Oops!") buildCookBook . mapM (parseIt reactionP)
checkExample :: CookBook -> Int -> IO ()
checkExample cb ore = do
let lab = produce (1 :* "FUEL") cb cleanLab
unless (count labSource "ORE" lab == ore) $ do
putStrLn "---/nSources:"
mapM_ print $ Map.toList $ labSource lab
putStrLn "---/nPool:"
mapM_ print $ Map.toList $ labPool lab