/
LastPiece.hs
313 lines (250 loc) · 9 KB
/
LastPiece.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
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NegativeLiterals #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -fno-specialise #-}
{-# OPTIONS_GHC -fno-strictness #-}
{-% Last piece puzzle, adapted from nofib/spectral/last-piece.
This is a solver for a jigsaw problem:
see https://www.nicklevine.org/contest/2003/index.html.
I've removed prettyprinting code for solutions and replaced Map.Map with an
association list. The original version collected the entire search tree,
including paths which led to failure, and the PLC version quickly ran out of
memory. This version prunes the search tree to keep only successful paths. It
still doesn't work on the CEK machine (I don't know about the CK machine: that
took forever).
%-}
module PlutusBenchmark.NoFib.LastPiece where
import PlutusBenchmark.Common (Term, compiledCodeToTerm)
import Data.Char (isSpace)
import PlutusCore.Pretty qualified as PLC
import PlutusTx as PlutusTx
import PlutusTx.Builtins as Tx
import PlutusTx.Prelude as PLC hiding (Semigroup (..), check, foldMap)
import Prelude qualified as Haskell
-------------------------------------
-- Pieces
type Offset = (Integer, Integer)
type Square = (Integer, Integer)
-- (1,1) is bottom LH corner
type PieceId = Tx.BuiltinString
type Board = [(Square, PieceId)] -- Was Map.Map Square PieceId
data Piece = P PieceId
[[Offset]] -- Male in bottom LH
[[Offset]] -- Female in bottom LH
-- In both cases, the list of offset is all the
-- squares except the bottom LH one
data Solution = Soln Board
| Choose [Solution] -- Non-empty
| Fail -- Board Square
deriving stock (Haskell.Show)
data Sex = Male | Female
{-# INLINABLE sumList #-}
sumList :: [Integer] -> Integer
sumList [] = 0
sumList (h:t) = h + sumList t
{-# INLINABLE numSolutions #-}
numSolutions :: Solution -> Integer
numSolutions (Soln _) = 1
numSolutions (Choose l) = sumList . map numSolutions $ l
numSolutions Fail = 0
sizeOfSolution :: Solution -> Integer
sizeOfSolution (Soln _) = 1
sizeOfSolution (Choose l) = sumList . map sizeOfSolution $ l
sizeOfSolution Fail = 1
{-# INLINABLE flipSex #-}
flipSex :: Sex -> Sex
flipSex Male = Female
flipSex Female = Male
-- The main search
{-# INLINABLE search #-}
search :: Square -> Sex -- Square we are up to
-> Board -- Current board
-> [Piece] -- Remaining pieces
-> Solution
search _ _ board []
= Soln board -- Finished
search (row,col) sex board ps -- Next row
| col == (maxCol+1) = search (row+1, 1) (flipSex sex) board ps
search square sex board ps -- Occupied square
| isJust (check board square) = search (next square) (flipSex sex) board ps
search square sex board ps
= case mapMaybe (try square sex board) choices of
[] -> Fail -- board square
ss -> prune ss -- discard failed paths
where
choices = [(pid, os, ps') |
(P pid ms fs, ps') <- pickOne ps,
let oss = case sex of
Male -> ms
Female -> fs,
os <- oss]
{-# INLINABLE prune #-}
-- % An attempt to cut down on the size of the result (not in the original program)
prune :: [Solution] -> Solution
prune ss =
case filter nonFailure ss of
[] -> Fail
[Soln s] -> Soln s
l -> Choose l
where nonFailure Fail = False
nonFailure _ = True
{-# INLINABLE try #-}
try :: Square -> Sex -> Board -> (PieceId,[Offset],[Piece]) -> Maybe Solution
try square sex board (pid,os,ps)
= case fit board square pid os of
Just board' -> Just (search (next square) (flipSex sex) board' ps)
Nothing -> Nothing
{-# INLINABLE fit #-}
fit :: Board -> Square -> PieceId -> [Offset] -> Maybe Board
fit board square pid [] = Just (extend board square pid)
fit board square pid (o:os) =
case extend_maybe board (square `add` o) pid of
Just board' -> fit board' square pid os
Nothing -> Nothing
--------------------------
-- Offsets and squares
{-# INLINABLE add #-}
add :: Square -> Offset -> Square
add (row,col) (orow, ocol) = (row + orow, col + ocol)
{-# INLINABLE next #-}
next :: Square -> Square
next (row,col) = (row,col+1)
{-# INLINABLE maxRow #-}
{-# INLINABLE maxCol #-}
maxRow,maxCol :: Integer
maxRow = 8
maxCol = 8
------------------------
-- Boards
{-# INLINABLE emptyBoard #-}
emptyBoard :: Board
emptyBoard = [] -- Map.empty
{-# INLINABLE check #-}
check :: Board -> Square -> Maybe PieceId
check board square = -- Map.lookup square board
case board of
[] -> Nothing
(square',pid):board' -> if square == square' then Just pid else check board' square
{-# INLINABLE extend #-}
extend :: Board -> Square -> PieceId -> Board
extend board square pid = (square, pid): board -- Map.insert square pid board
{-# INLINABLE extend_maybe #-}
extend_maybe :: Board -> Square -> PieceId -> Maybe Board
extend_maybe board square@(row,col) pid
| row > maxRow || col < 1 || col > maxCol
= Nothing
| otherwise
= case check board square of
Just _ -> Nothing
Nothing -> Just (extend board square pid)
--------------------------
-- Utility
{-# INLINABLE pickOne #-}
pickOne :: [a] -> [(a,[a])]
pickOne = go id
where
go _ [] = []
go f (x:xs) = (x, f xs) : go ((x :) . f) xs
-----------------------------------
-- The initial setup
{-# INLINABLE fromJust #-}
-- % Library functions is not inlinable
fromJust :: Maybe a -> a
fromJust Nothing = Tx.error ()
fromJust (Just x) = x
{-# INLINABLE initialBoard #-}
initialBoard :: Board
initialBoard = fromJust (fit emptyBoard (1,1) "a" [(1,0),(1,1)])
{-# INLINABLE initialPieces #-}
initialPieces :: [Piece]
initialPieces = [bPiece, cPiece, dPiece, ePiece, fPiece,
gPiece, hPiece, iPiece, jPiece, kPiece,
lPiece, mPiece, nPiece]
{-# INLINABLE nPiece #-}
nPiece :: Piece
nPiece = P "n" [ [(0,1),(1,1),(2,1),(2,2)],
[(1,0),(1,-1),(1,-2),(2,-2)] ]
[]
{-# INLINABLE mPiece #-}
mPiece :: Piece
mPiece = P "m" [ [(0,1),(1,0),(2,0),(3,0)] ]
[ [(0,1),(0,2),(0,3),(1,3)],
[(1,0),(2,0),(3,0),(3,-1)] ]
{-# INLINABLE lPiece #-}
lPiece :: Piece
lPiece = P "l" [ [(0,1),(0,2),(0,3),(1,2)],
[(1,0),(2,0),(3,0),(2,-1)] ]
[ [(1,-1),(1,0),(1,1),(1,2)],
[(1,0),(2,0),(3,0),(1,1)] ]
{-# INLINABLE kPiece #-}
kPiece :: Piece
kPiece = P "k" [ [(0,1),(1,0),(2,0),(2,-1)] ]
[ [(1,0),(1,1),(1,2),(2,2)] ]
{-# INLINABLE jPiece #-}
jPiece :: Piece
jPiece = P "j" [ [(0,1),(0,2),(0,3),(1,1)],
[(1,0),(2,0),(3,0),(1,-1)],
[(1,-2),(1,-1),(1,0),(1,1)] ]
[ [(1,0),(2,0),(3,0),(2,2)] ]
{-# INLINABLE iPiece #-}
iPiece :: Piece
iPiece = P "i" [ [(1,0),(2,0),(2,1),(3,1)],
[(0,1),(0,2),(1,0),(1,-1)],
[(1,0),(1,1),(2,1),(3,1)] ]
[ [(0,1),(1,0),(1,-1),(1,-2)] ]
{-# INLINABLE hPiece #-}
hPiece :: Piece
hPiece = P "h" [ [(0,1),(1,1),(1,2),(2,2)],
[(1,0),(1,-1),(2,-1),(2,-2)],
[(1,0),(1,1),(2,1),(2,2)] ]
[ [(0,1),(1,0),(1,-1),(2,-1)] ]
{-# INLINABLE gPiece #-}
gPiece :: Piece
gPiece = P "g" [ ]
[ [(0,1),(1,1),(1,2),(1,3)],
[(1,0),(1,-1),(2,-1),(3,-1)],
[(0,1),(0,2),(1,2),(1,3)],
[(1,0),(2,0),(2,-1),(3,-1)] ]
{-# INLINABLE fPiece #-}
fPiece :: Piece
fPiece = P "f" [ [(0,1),(1,1),(2,1),(3,1)],
[(1,0),(1,-1),(1,-2),(1,-3)],
[(1,0),(2,0),(3,0),(3,1)] ]
[ [(0,1),(0,2),(0,3),(1,0)] ]
{-# INLINABLE ePiece #-}
ePiece :: Piece
ePiece = P "e" [ [(0,1),(1,1),(1,2)],
[(1,0),(1,-1),(2,-1)] ]
[ [(0,1),(1,1),(1,2)],
[(1,0),(1,-1),(2,-1)] ]
{-# INLINABLE dPiece #-}
dPiece :: Piece
dPiece = P "d" [ [(0,1),(1,1),(2,1)],
[(1,0),(1,-1),(1,-2)] ]
[ [(1,0),(2,0),(2,1)] ]
{-# INLINABLE cPiece #-}
cPiece :: Piece
cPiece = P "c" [ ]
[ [(0,1),(0,2),(1,1)],
[(1,0),(1,-1),(2,0)],
[(1,-1),(1,0),(1,1)],
[(1,0),(1,1),(2,0)] ]
{-# INLINABLE bPiece #-}
bPiece :: Piece
bPiece = P "b" [ [(0,1),(0,2),(1,2)],
[(1,0),(2,0),(2,-1)],
[(0,1),(1,0),(2,0)] ]
[ [(1,0),(1,1),(1,2)] ]
unindent :: PLC.Doc ann -> [Haskell.String]
unindent d = map (Haskell.dropWhile isSpace) (Haskell.lines . Haskell.show $ d)
runLastPiece :: Solution
runLastPiece = search (1,2) Female initialBoard initialPieces
mkLastPieceTerm :: Term
mkLastPieceTerm =
compiledCodeToTerm $ $$(compile [|| runLastPiece ||])
-- -- Number of correct solutions: 3
-- -- Number including failures: 59491