-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDay12.hs
152 lines (126 loc) · 4.73 KB
/
Day12.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
module Javran.AdventOfCode.Y2018.Day12 (
) where
import Control.Monad
import Data.Function
import qualified Data.IntSet as IS
import Data.List.Split hiding (sepBy)
import qualified Data.Map.Strict as M
import Javran.AdventOfCode.Prelude
import Text.ParserCombinators.ReadP hiding (count, get, many)
data Day12 deriving (Generic)
boolP :: ReadP Bool
boolP = (False <$ char '.') <++ (True <$ char '#')
initStateP :: ReadP [Bool]
initStateP = string "initial state: " *> many1 boolP
ruleP :: ReadP ([Bool], Bool)
ruleP = do
lhs <- many1 boolP
_ <- string " => "
rhs <- boolP
pure (lhs, rhs)
_pprWorld :: World -> String
_pprWorld w = case IS.minView w of
Nothing -> ""
Just (minLoc, _) ->
let maxLoc = IS.findMax w
in do
l <- [minLoc .. maxLoc]
pure $ bool '.' '#' (IS.member l w)
type Rules = M.Map [Bool] Bool
verifyRules :: Rules -> Either String Rules
verifyRules rules = do
let enforce rawRule = do
let (lhs, expectedRhs) = consumeOrDie ruleP rawRule
actualRhs = Just True == rules M.!? lhs
when (expectedRhs /= actualRhs) do
Left $ "Unexpected rule for " <> show lhs
enforce "..... => ."
enforce "##### => ."
enforce "....# => ."
enforce "#.... => ."
pure rules
{-
For part 2, obviously simulation won't cut it,
so I suspected there was an attractor.
And indeed there is, with a catch: eventually
the relative location between points are fixed,
but the pattern as a whole moves in one direction indefinitely.
Therefore in fact we not only have an attractor, but also
it is simply a fixpoint with a constant "drift".
In order to find this fixpoint, we just need to change our view
of the World, literally:
we change the representation from World to RebasedWorld = (offset, World),
so that the resulting world's minimum location is always 0.
This allows us to focus on the resulting World and see that indeed relative
locations won't change beyond fixpoint generation.
-}
type World = IS.IntSet
step :: Rules -> World -> World
step rules w = case IS.minView w of
Nothing -> w
Just (minLoc, _) ->
let maxLoc = IS.findMax w
in IS.fromDistinctAscList do
loc <- [minLoc - 1 .. maxLoc + 1]
{-
we technically need to examine `minLoc - 2` and `maxLoc + 2`,
in case the following is part of the rule:
....# => #
#.... => #
however, for both example and my login, we have the following
as part of the rule:
....# => .
#.... => .
meaning we can cut padding to just 3.
-}
let locView = fmap (`IS.member` w) [loc - 2 .. loc + 2]
loc <$ guard (Just True == (rules M.!? locView))
{-
A rebased world always have the minimal location being 0,
the offset is tracked as a seperated Int.
-}
type RebasedWorld = (Int, World)
{-
Normalizes the minimal location so that it is always 0.
-}
rebase :: World -> RebasedWorld
rebase w = case IS.minView w of
Nothing -> (0, w)
Just (minLoc, _) -> (minLoc, IS.mapMonotonic (subtract minLoc) w)
rWorldSum :: RebasedWorld -> Int
rWorldSum (offset, w) = offset * IS.size w + sum (IS.toList w)
instance Solution Day12 where
solutionRun _ SolutionContext {getInputS, answerShow} = do
[[initStRaw], rulesRaw] <- splitOn [""] . lines <$> getInputS
let parsedSt = consumeOrDie initStateP initStRaw
initSt =
IS.fromDistinctAscList $
catMaybes $
zipWith (\v i -> if v then Just i else Nothing) parsedSt [0 ..]
rulesIn =
M.fromListWith (error "rule conflict") $
fmap (consumeOrDie ruleP) rulesRaw
rules = case verifyRules rulesIn of
Right v -> v
Left msg -> error $ "Failed to verify rule: " <> msg
progression :: [(Int, RebasedWorld)]
progression = fmap (second rebase) $ zip [0 ..] $ iterate (step rules) initSt
do
let (_, w) = progression !! 20
answerShow $ rWorldSum w
do
let ((startGen, (minLocBefore, w)), (_, (minLocAfter, _))) : _ =
dropWhile (uncurry ((/=) `on` (snd . snd))) $ zip progression (tail progression)
incr = minLocAfter - minLocBefore
slowSolve targetGen =
-- we should get the exact same result as fastSolve beyond fixpoint.
rWorldSum $ snd $ progression !! targetGen
fastSolve targetGen = rWorldSum (minLocBefore + incr * (targetGen - startGen), w)
verify = False
{-
Starting from generation startGen,
we have a fixpoint that "drifts" by (minLocAfter - minLocBefore) everytime.
-}
when verify $
print (all (\n -> fastSolve n == slowSolve n) (take 20 [startGen ..]))
answerShow $ fastSolve 50000000000