-
Notifications
You must be signed in to change notification settings - Fork 0
/
Day21.hs
220 lines (194 loc) · 6.39 KB
/
Day21.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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
module Day21
( part1
, part2
) where
import Data.Either (fromRight)
import Data.IntMap as I (IntMap, assocs, fromList,
insert, (!))
import Data.List (foldl')
import Data.Map as M (Map, assocs, fromList, insert,
(!))
import Data.Text (Text, pack)
import Data.Tuple (swap)
import Helpers.Parsers.Text (Parser)
import Text.Megaparsec (eof, manyTill, optional, parse,
(<|>))
import Text.Megaparsec.Char (char, eol, lowerChar, string)
import Text.Megaparsec.Char.Lexer (decimal)
data Operation
= SwapPos Int Int
| SwapLetter Char Char
| RotateLeft Int
| RotateRight Int
| RotatePos Char
| Reverse Int Int
| Move Int Int
deriving (Eq, Ord, Show)
data Hash =
Hash (IntMap Char) (Map Char Int)
instance Show Hash where
show (Hash indexMap _) = map snd . I.assocs $ indexMap
password test =
Hash
(I.fromList . assocList $ test)
(M.fromList . map swap . assocList $ test)
assocList test
| test = zip [0 ..] "abcde"
| otherwise = zip [0 ..] "abcdefgh"
scrambledList = zip [0 ..] "fbgdceah"
scrambled =
Hash (I.fromList scrambledList) (M.fromList . map swap $ scrambledList)
hashInsert :: Int -> Char -> Hash -> Hash
hashInsert x a (Hash indexMap charMap) =
Hash (I.insert x a indexMap) (M.insert a x charMap)
parseInput :: Parser [Operation]
parseInput = manyTill parseOp eof
parseOp :: Parser Operation
parseOp =
parseSwapPos
<|> parseSwapLetter
<|> parseRotateLeft
<|> parseRotateRight
<|> parseRotatePos
<|> parseReverse
<|> parseMove
parseSwapPos :: Parser Operation
parseSwapPos = do
string . pack $ "swap position "
x <- decimal
string . pack $ " with position "
y <- decimal
eol
return . SwapPos x $ y
parseSwapLetter :: Parser Operation
parseSwapLetter = do
string . pack $ "swap letter "
x <- lowerChar
string . pack $ " with letter "
y <- lowerChar
eol
return . SwapLetter x $ y
parseRotateLeft :: Parser Operation
parseRotateLeft = do
string . pack $ "rotate left "
x <- decimal
string . pack $ " step"
optional . char $ 's'
eol
return . RotateLeft $ x
parseRotateRight :: Parser Operation
parseRotateRight = do
string . pack $ "rotate right "
x <- decimal
string . pack $ " step"
optional . char $ 's'
eol
return . RotateRight $ x
parseRotatePos :: Parser Operation
parseRotatePos = do
string . pack $ "rotate based on position of letter "
x <- lowerChar
eol
return . RotatePos $ x
parseReverse :: Parser Operation
parseReverse = do
string . pack $ "reverse positions "
x <- decimal
string . pack $ " through "
y <- decimal
eol
return . Reverse x $ y
parseMove :: Parser Operation
parseMove = do
string . pack $ "move position "
x <- decimal
string . pack $ " to position "
y <- decimal
eol
return . Move x $ y
makeOperation :: [Operation] -> (Hash -> Hash)
makeOperation = foldl' (\ops op -> operation op . ops) id
makeInverseOperations :: [Operation] -> (Hash -> Hash)
makeInverseOperations = foldr (\op ops -> invert op . ops) id
hashPassword :: Hash -> [Operation] -> Hash
hashPassword hash operations = makeOperation operations hash
unhashPassword :: Hash -> [Operation] -> Hash
unhashPassword hash operations = makeInverseOperations operations hash
operation :: Operation -> Hash -> Hash
operation (SwapPos x y) hash@(Hash indexMap _) =
hashInsert y a . hashInsert x b $ hash
where
a = indexMap I.! x
b = indexMap I.! y
operation (SwapLetter a b) hash@(Hash _ charMap) =
hashInsert y a . hashInsert x b $ hash
where
x = charMap M.! a
y = charMap M.! b
operation (RotateLeft x) (Hash indexMap _) =
Hash (I.fromList assocsList') (M.fromList . map swap $ assocsList')
where
assocsList = I.assocs indexMap
assocsList' =
map (\(i, c) -> ((i - x) `mod` length assocsList, c)) assocsList
operation (RotateRight x) (Hash indexMap _) =
Hash (I.fromList assocsList') (M.fromList . map swap $ assocsList')
where
assocsList = I.assocs indexMap
assocsList' =
map (\(i, c) -> ((i + x) `mod` length assocsList, c)) assocsList
operation (RotatePos c) hash@(Hash _ charMap) = operation (RotateRight i') hash
where
i = charMap M.! c
i'
| i >= 4 = i + 2
| otherwise = i + 1
operation (Reverse x y) hash@(Hash indexMap _) =
foldr (uncurry hashInsert) hash reversed
where
reversed = zip [y,y - 1 .. x] . map (indexMap I.!) $ [x,x + 1 .. y]
operation (Move x y) hash@(Hash indexMap _) =
foldr (uncurry hashInsert) hash moved
where
moved = zip newIndices . map (indexMap I.!) $ movedIndices
movedIndices
| x < y = [x + 1 .. y] ++ [x]
| otherwise = x : [y .. (x - 1)]
newIndices
| x < y = [x .. y]
| otherwise = [y .. x]
invert :: Operation -> Hash -> Hash
invert op@(SwapPos _ _) hash = operation op hash
invert op@(SwapLetter _ _) hash = operation op hash
invert (RotateLeft x) hash = operation (RotateRight x) hash
invert (RotateRight x) hash = operation (RotateLeft x) hash
invert op@(Reverse _ _) hash = operation op hash
invert (Move x y) hash = operation (Move y x) hash
invert (RotatePos c) hash@(Hash _ charMap) = operation (RotateLeft i') hash
where
-- if i was greater than 4, then the new index will be 2*i + 2 `mod` 8, so
-- if i = 4 + k, the new index is going to be 2*k + 2, with 0 <= k <= 3, and
-- 0 <= new i <= 6, even new i. So we have k = (div (new i) 2 - 1) `mod` 8
-- and i = 4 + k. We want to rotate (i + 2) to the left, that is 5 + div i'
-- 2. We need to make a special case for i' == 0, which is actually i' == 8.
-- As 5 + 4 = 9, that's 1.
-- if i was less than 4, then the new index is 2*i + 1. We have i = div (new i -
-- 1) 2, and we want to rotate back i + 1, which is going to be div (new i +
-- 1) 2
i = charMap M.! c
i'
| i == 0 = 1
| even i = 5 + div i 2
| odd i = div (i + 1) 2
part1 :: Bool -> Text -> String
part1 test =
show
. hashPassword (password test)
. fromRight (error "parse failed")
. parse parseInput "day21"
part2 :: Bool -> Text -> String
part2 _ =
show
. unhashPassword scrambled
. fromRight (error "parse failed")
. parse parseInput "day21"