-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Misc2.hs
282 lines (249 loc) · 11.2 KB
/
Misc2.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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
module Misc2 (fullWords, isIdentifier, sudoku, crossword) where
import qualified Control.Monad as M
import qualified Data.Char as C
import qualified Data.List as L
import qualified Data.Maybe as Mb
import qualified Data.Vector as V
import qualified Data.Vector.Unboxed as VU
{-
Problem 95: (**) English number words.
On financial documents, like cheques, numbers must sometimes be written in full words.
Example: 175 must be written as one-seven-five.
Write a predicate full-words/1 to print (non-negative) integer numbers in full words.
-}
fullWords :: Int -> String
fullWords 0 = "zero"
fullWords x = (L.intercalate "-" . reverse) $ L.unfoldr go x
where
go :: Int -> Maybe (String, Int)
go 0 = Nothing
go n = (,left) <$> L.lookup right digit2Word
where
(left, right) = n `divMod` 10
digit2Word =
[ (1, "one"),
(2, "two"),
(3, "three"),
(4, "four"),
(5, "five"),
(6, "six"),
(7, "seven"),
(8, "eight"),
(9, "nine")
]
{-
Problem 96: (**) Syntax checker.
In a certain programming language (Ada) identifiers are defined by the syntax diagram below.
┌───────────────────────────────┐
│ │
│ │
┌▼┐ │
┌─────────►└┬┴───────┐ │
│ │ │ │
│ │ ▼ │
┌──────┬┐ ┌───┴───┐ │ ┌┬┐ ┌───────┐ │ ┌┬──────┐
│begin │┼─────►letter │ │ └┴┴┬─────►letter ├────►┌┼┬───►│end │
└──────┴┘ └───────┘ │ ▲ │ └───────┘ └┴┘ └┴──────┘
│ │ │ ▲
┌──▼────┐ │ │ ┌───────┐ │
│ _ ├───┘ └─────►digit │ │
└───────┘ └───────┴──────┘
Write a function which checks whether a given string is a legal identifier.
ANSWER:
We translate the above syntax diagram into the following state transition diagram.
States are shown as boxes, and the transitions are shown as arrows between the boxes.
State transitions are caused by reading the next character, if any.
The state transition rules are shown as edge labels.
┌───────────────────────────────┐
│ │
│ not "" │
│ │
┌──────┐letter ┌──────┐not "" ┌▼─────┐not '_'┌──────┐letter┌───┴──┐
│ begin├────────► S1 ├────────► s2 ├───────► s3 ├──────► s4 │
└──────┘ └───┬──┘ └──────┘ '_' └──────┘digit └───┬──┘
│ │
""│ ""│
│ │
│ ┌──────┐ │
└───────────► end ◄─────────────────────────┘
└──────┘
The "not" transitions don't consume any input. For example, from S2,
if the next character is an underscore, then it transitions to S3 with the
remaining string, otherwise it transitions to S3 with the same input that
it had received.
Same for S1-(not "")-> S2, and S4-(not "")-> S2.
"" indicates the end of string.
Reading a character not matching one of the defined transition rules for
that state causes a transition to the error state (not shown in the diagram).
-}
data ParserState = Begin | S1 | S2 | S3 | S4 | End
parseId :: ParserState -> String -> Maybe (ParserState, String)
parseId s xs = case (s, xs) of
(Begin, x : ys) -> if C.isLetter x then Just (S1, ys) else Nothing
(S1, "") -> Just (End, "")
(S1, ys) -> Just (S2, ys)
(S2, x : ys) -> if x == '_' then Just (S3, ys) else Just (S3, x : ys)
(S3, x : ys) -> if C.isLetter x || C.isDigit x then Just (S4, ys) else Nothing
(S4, "") -> Just (End, "")
(S4, ys) -> Just (S2, ys)
(_, _) -> Nothing
isIdentifier :: String -> Bool
isIdentifier = go Begin
where
go s xs = case parseId s xs of
Just (End, "") -> True
Just (s', ys) -> go s' ys
_ -> False
{-
Problem 97: (**) Sudoku.
ANSWER:
Backtracking. The candidates for a cell are the numbers _not_
present in the same row, column, and 3x3 box. Finding the
numbers in the same row and column are easy. For the box,
we first first its top-left coordinate, then iterate
left-to-right and top-to-bottom.
We start from the top-left cell, and for every cell containing
a zero, fill it with one of the candidates, and move on to the
next empty cell. If at any point, there are no candidates
available for a cell, we backtrack, and try the next candidate
for the previous cell.
-}
sudoku :: [[Int]] -> [[Int]]
sudoku grid = (V.toList . V.map VU.toList) (Mb.fromJust soln)
where
soln = solve 0 0 initialBoard
initialBoard = V.fromList $ map VU.fromList grid
row = flip (V.!)
col n = V.map (VU.! n)
box r c board = [get r' c' board | r' <- [x .. x + 2], c' <- [y .. y + 2]]
where
x = 3 * (r `div` 3)
y = 3 * (c `div` 3)
isValid r c val board =
val `VU.notElem` row r board
&& val `V.notElem` col c board
&& val `notElem` box r c board
candidates r c board = [x | x <- [1 .. 9], isValid r c x board]
get r c board = (board V.! r) VU.! c
set r c val board =
V.update board $
V.singleton
( r,
VU.update (row r board) $
VU.singleton (c, val)
)
solve r c board
| r > 8 = Just board
| c > 8 = solve (r + 1) 0 board
| get r c board /= 0 = solve r (c + 1) board
| otherwise = M.msum $ do
x <- candidates r c board
let b = set r c x board
return $ solve r (c + 1) b
{-
Problem 98: (***) Nonograms.
The puzzle goes like this: Essentially, each row and column of a rectangular bitmap
is annotated with the respective lengths of its distinct strings of occupied cells.
The person who solves the puzzle must complete the bitmap given only these lengths.
Example:
Problem: Solution:
|_|_|_|_|_|_|_|_| 3 |_|X|X|X|_|_|_|_| 3
|_|_|_|_|_|_|_|_| 2 1 |X|X|_|X|_|_|_|_| 2 1
|_|_|_|_|_|_|_|_| 3 2 |_|X|X|X|_|_|X|X| 3 2
|_|_|_|_|_|_|_|_| 2 2 |_|_|X|X|_|_|X|X| 2 2
|_|_|_|_|_|_|_|_| 6 |_|_|X|X|X|X|X|X| 6
|_|_|_|_|_|_|_|_| 1 5 |X|_|X|X|X|X|X|_| 1 5
|_|_|_|_|_|_|_|_| 6 |X|X|X|X|X|X|_|_| 6
|_|_|_|_|_|_|_|_| 1 |_|_|_|_|X|_|_|_| 1
|_|_|_|_|_|_|_|_| 2 |_|_|_|X|X|_|_|_| 2
1 3 1 7 5 3 4 3 1 3 1 7 5 3 4 3
2 1 5 1 2 1 5 1
3 against the 1st row means there should be 3 consecutive 'X's in the solution.
2 1 against the 2nd row means there should be 2 consecutive 'X's, followed by
_at least_ one space, then another 'X'.
The numbers at the bottom are the constraints for the corresponding columns.
ANSWER: TODO.
-}
{-
Problem 99: (***) Crossword puzzle.AnnotatedTree.
Given an empty (or almost empty) framework of a crossword puzzle and a set of words.
The problem is to place the words into the framework.
Words are strings (character lists) of at least two characters.
A horizontal or vertical sequence of character places in the crossword puzzle framework
is called a site. Our problem is to find a compatible way of placing words onto sites.
For efficiency reasons it is important, at least for larger puzzles, to sort the words
and the sites in a particular order. For this part of the problem, the solution of P28
may be very helpful.
ANSWER:
Backtracking.
- Instead of repeated updates of the grid, using modifiable array/vector would probably
be faster.
- Another improvement (?) could be to find the sites and match them with words of similar
lengths.
-}
crossword :: [String] -> [String] -> [String]
crossword words' grid = (V.toList . V.map VU.toList) (Mb.fromJust soln)
where
soln = solve 0 0 words' initialBoard
initialBoard = V.fromList $ map VU.fromList grid
m = V.length initialBoard
n = VU.length $ V.head initialBoard
row = flip (V.!)
col c = V.map (VU.! c)
get r c board = (board V.! r) VU.! c
-- The word can fit starting at cell (r, c) if
-- there's enough empty space for the whole word,
-- or there's a prefix match, and there's space
-- for the remaining suffix.
isFitHorz word r c board =
c <= (n - k)
&& all (\(x, y) -> C.isSpace x || x == y) ys
where
k = length word
xs = VU.slice c k (row r board)
ys = zip (VU.toList xs) word
isFitVert word r c board =
r <= (m - k)
&& all (\(x, y) -> C.isSpace x || x == y) ys
where
k = length word
xs = col c $ V.slice r k board
ys = zip (V.toList xs) word
placeHorz word r c board =
V.update board $
V.singleton
( r,
VU.update (row r board) $
VU.fromList (zip [c ..] word)
)
placeVert word r c board =
V.update board $
V.fromList $
zipWith (\i x -> (i, updateRow i x)) [r ..] word
where
updateRow i x =
VU.update (row i board) $
VU.singleton (c, x)
solve _ _ [] board = Just board
solve r c w@(x : xs) board
| r >= m = Nothing
| c >= n = solve (r + 1) 0 w board
| (== '.') (get r c board) = solve r (c + 1) w board
| horzFit || vertFit = do
let placements = [(horzFit, placeHorz), (vertFit, placeVert)]
let res = M.msum $ do
(fit, place) <- placements
M.guard fit
-- Place the word, and start from
-- the top with the remaining words.
let b = place x r c board
return $ solve 0 0 xs b
case res of
-- Placing the word here didn't work,
-- find another place.
Nothing -> solve r (c + 1) w board
b -> b
| otherwise = solve r (c + 1) w board
where
horzFit = isFitHorz x r c board
vertFit = isFitVert x r c board