File: DancingSudoku.lhs

package info (click to toggle)
pygments 0.10-1
  • links: PTS, VCS
  • area: main
  • in suites: lenny
  • size: 3,748 kB
  • ctags: 3,621
  • sloc: python: 15,151; ansic: 3,408; pascal: 2,750; sh: 2,244; ruby: 2,130; lisp: 1,839; xml: 1,797; java: 1,742; cpp: 1,549; ml: 831; haskell: 721; csh: 681; f90: 451; perl: 375; php: 252; cs: 225; erlang: 104; makefile: 84; jsp: 21
file content (411 lines) | stat: -rw-r--r-- 15,231 bytes parent folder | download | duplicates (2)
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
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
 A Sukodku solver by Chris Kuklewicz (haskell (at) list (dot) mightyreason (dot) com)
 The usual BSD license applies, copyright 2006.
 Uploaded to HaskellWiki as DancingSudoku.lhs

 I compile on a powerbook G4 (Mac OS X, ghc 6.4.2) using
 ghc -optc-O3 -funbox-strict-fields -O2 --make -fglasgow-exts

 This is a translation of Knuth's GDANCE from dance.w / dance.c

 http://www-cs-faculty.stanford.edu/~uno/preprints.html
 http://www-cs-faculty.stanford.edu/~uno/programs.html
 http://en.wikipedia.org/wiki/Dancing_Links

 I have an older verison that uses lazy ST to return the solutions on
 demand, which was more useful when trying to generate new puzzles to
 solve.

> module Main where

> import Prelude hiding (read)
> import Control.Monad
> import Control.Monad.Fix
> import Data.Array.IArray
> import Control.Monad.ST.Strict
> import Data.STRef.Strict
> import Data.Char(intToDigit,digitToInt)
> import Data.List(unfoldr,intersperse,inits)

> new = newSTRef
> {-# INLINE new #-}
> read = readSTRef
> {-# INLINE read #-}
> write = writeSTRef
> {-# INLINE write #-}
> modify = modifySTRef
> {-# INLINE modify #-}

 Data types to prevent mixing different index and value types

> type A = Int
> newtype R = R A deriving (Show,Read,Eq,Ord,Ix,Enum)
> newtype C = C A deriving (Show,Read,Eq,Ord,Ix,Enum)
> newtype V = V A deriving (Show,Read,Eq,Ord,Ix,Enum)
> newtype B = B A deriving (Show,Read,Eq,Ord,Ix,Enum)

 Sudoku also has block constraints, so we want to look up a block
 index in an array:

> lookupBlock :: Array (R,C) B
> lookupBlock = listArray bb [ toBlock ij | ij <- range bb ]
>     where ra :: Array Int B
>           ra = listArray (0,pred (rangeSize b)) [B (fst b) .. B (snd b)]
>           toBlock (R i,C j) = ra ! ( (div (index b j) 3)+3*(div (index b i) 3) )

 The values for an unknown location is 'u'.
 The bound and range are given by b and rng.  And bb is a 2D bound.

> u = V 0  -- unknown value
> b :: (Int,Int)
> b = (1,9) -- min and max bounds
> rng = enumFromTo (fst b)  (snd b)  -- list from '1' to '9'
> bb = ((R (fst b),C (fst b)),(R (snd b),C (snd b)))

  A Spec can be turned into a parsed array with ease:

> type Hint = ((R,C),V)
> newtype Spec = Spec [Hint] deriving (Eq,Show)

> type PA = Array (R,C) V

> parse :: Spec -> PA
> parse (Spec parsed) = let acc old new = new
>                       in accumArray acc u bb parsed

 The dancing links algorithm depends on a sparse 2D node structure.
 Each column represents a constraint.  Each row represents a Hint.
 The number of possible hints is 9x9x9 = 271

> type (MutInt st)  = (STRef st) Int

 The pointer types:

> type (NodePtr st) = (STRef st) (Node st)
> type (HeadPtr st)  = (STRef st) (Head st)

 The structures is a 2D grid of nodes, with Col's on the top of
 columns and a sparse collection of nodes.  Note that topNode of Head
 is not a strict field.  This is because the topNode needs to refer to
 the Head, and they are both created monadically.

> type HeadName = (Int,Int,Int) -- see below for meaning

> data Head st = Head {headName:: !HeadName
>                     ,topNode:: (Node st) -- header node for this column
>                     ,len:: !(MutInt st)  -- number of nodes below this head
>                     ,next,prev:: !(HeadPtr st)  -- doubly-linked list
>                     }

> data Node st = Node {getHint:: !Hint
>                     ,getHead:: !(Head st)  -- head for the column this node is in
>                     ,up,down,left,right :: !(NodePtr st)  -- two doubly-linked lists
>                     }

> instance Eq (Head st) where
>     a == b = headName a == headName b

> instance Eq (Node st) where
>     a == b = up a == up b

 To initialize the structures is a bit tedious.  Knuth's code reads in
 the problem description from a data file and builds the structure
 based on that.  Rather than short strings, I will use HeadName as the
 identifier.
 
 The columns are (0,4,5) for nodes that put some value in Row 4 Col 5
                 (1,2,3) for nodes that put Val 3 in Row 2 and some column
                 (2,7,4) for nodes that put Val 4 in Col 7 and some row
                 (3,1,8) for nodes that put Val 8 in some (row,column) in Block 1

 The first head is (0,0,0) which is the root.  The non-root head data
 will be put in an array with the HeadName as an index.

> headNames :: [HeadName]
> headNames = let names = [0,1,2,3] 
>             in (0,0,0):[ (l,i,j) | l<-names,i<-rng,j<-rng]

 A "row" of left-right linked nodes is a move.  It is defined by a
 list of head names.

> type Move = [(Hint,HeadName)]

 Initial hints are enforced by making them the only legal move for
 that location.  Blank entries with value 'u = V 0' have a move for
 all possible values [V 1..V 9].

> parseSpec :: Spec -> [Move]
> parseSpec spec =
>   let rowsFrom :: Hint -> [Move]
>       rowsFrom (rc@(R r,C c),mv@(V v')) = 
>           if mv == u then [ rsyms v | v <- rng ]
>           else [ rsyms v' ]
>         where (B b) = lookupBlock ! rc
>               rsyms :: A -> Move
>               rsyms v = map ( (,) (rc,V v) ) [(0,r,c),(1,r,v),(2,c,v),(3,b,v)]
>   in concatMap rowsFrom (assocs (parse spec))

 mkDList creates doubly linked lists using a monadic smart
 constructor and the recursive "mdo" notation as documented at
 http://www.haskell.org/ghc/docs/latest/html/users_guide/syntax-extns.html#mdo-notation
 http://www.cse.ogi.edu/PacSoft/projects/rmb/

 For more fun with this, see the wiki page at
 http://haskell.org/hawiki/TyingTheKnot

> mkDList :: (MonadFix m) => (b -> a -> b -> m b) -> [a] -> m b
> mkDList _ [] = error "must have at least one element"
> mkDList mkNode xs = mdo (first,last) <- go last xs first
>                         return first
>   where go prev []     next = return (next,prev)
>         go prev (x:xs) next = mdo this <- mkNode prev x rest
>                                   (rest,last) <- go this xs next
>                                   return (this,last)

 toSimple takes a function and a header node and iterates (read . function)
 until the header is reached again, but does not return the header
 itself.

> toSingle step header = loop =<< (read . step) header
>     where loop y = if header/=y then liftM (y:) (read (step y) >>= loop)
>                                 else return []
>        

 forEach is an optimization of (toSimple step header >>= mapM_ act)

> forEach step header act = loop =<< (read . step) header
>      where loop y = if header/=y then (act y >> (read (step y)) >>= loop)
>                                  else return ()

 Now make the root node and all the head nodes. This also exploits mdo:

> makeHeads :: [HeadName] -> (ST st) (Head st)
> makeHeads names = mkDList makeHead names
>     where makeHead before name after = mdo
>             ~newTopNode <- liftM4 (Node ((R 0,C 0),V 0) newHead) (new newTopNode) (new newTopNode)
>                                                                 (new newTopNode) (new newTopNode)
>             newHead <- liftM3 (Head name newTopNode)                         
>                                    (new 0) (new after) (new before)
>             return newHead

 The Head nodes will be places in an array for easy lookup while building moves:

> type HArray st = Array HeadName (Head st)
> hBounds = ((0,1,1),(3,9,9))
> type Root st =  (Head st,HArray st)

 The addMove function creates the (four) nodes that represent a move and adds
 them to the data structure.  The HArray in Root makes for a fast
 lookup of the Head data.

> addMove :: forall st. (Root st) -> Move -> (ST st) (Node st)
> addMove (_,ha) move = mkDList addNode move
>     where addNode :: (Node st) -> (Hint,HeadName) -> (Node st) -> (ST st) (Node st)
>           addNode before (hint,name) after = do
>             let head = ha ! name
>             let below = topNode head
>             above <- read (up below)
>             newNode <- liftM4 (Node hint head) (new above) (new below)
>                                                (new before) (new after)
>             write (down above) newNode
>             write (up below) newNode
>             modify (len head) succ
>             l <- read (len head)
>             seq l (return newNode)

 Create the column headers, including the fast lookup array.  These
 will be resused between puzzles.

> initHA :: (ST st) (Root st)
> initHA = do
>   root <- makeHeads headNames
>   heads <- toSingle next root
>   let ha = array hBounds (zip (map headName heads) heads)
>   return (root,ha)

 Take the Root from initHA and a puzzle Spec and fill in all the Nodes.

> initRoot :: (Root st) -> Spec -> (ST st) ()
> initRoot root spec = do
>   let moves = parseSpec spec
>   mapM_ (addMove root) moves

  Return the column headers to their condition after initHA

> resetRoot :: (Root st) -> (ST st) ()
> resetRoot (root,ha) = do
>   let heads@(first:_) = elems ha
>   let resetHead head = do
>         write (len head) 0
>         let node = topNode head
>         write (down node) node
>         write (up node) node
>       reset (last:[]) = do
>         write (prev root) last
>         write (next root) first
>       reset (before:xs@(head:[])) = do
>         resetHead head
>         write (prev head) before
>         write (next head) root
>         reset xs
>       reset (before:xs@(head:after:_)) = do
>         resetHead head
>         write (prev head) before
>         write (next head) after
>         reset xs
>   reset (root:heads)

 getBest iterates over the unmet constraints (i.e. the Head that are
 reachable from root). It locates the one with the lowest number of
 possible moves that will solve it, aborting early if it finds 0 or 1
 moves.

> getBest :: (Head st) -> (ST st) (Maybe (Head st))
> getBest root = do
>   first <- read (next root)
>   if first == root then return Nothing
>     else do
>       let findMin m best head | head == root = return (Just best)
>                               | otherwise = do
>             l <- read (len head)
>             if l <= 1 then return (Just head)
>               else if l < m then findMin l head =<< read (next head)
>                      else findMin l best =<< read (next head)
>       findMin 10 first first

 The unlink and relink operations are from where Knuth got the name
 "dancing links".  So long as "a" does not change in between, the
 relink call will undo the unlink call.  Similarly, the unconver will
 undo the changes of cover and unconverOthers will undo coverOthers.

> unlink :: (a->STRef st a) -> (a->STRef st a) -> a -> (ST st) ()
> unlink prev next a = do
>   before <- read (prev a)
>   after <- read (next a)
>   write (next before) after
>   write (prev after) before

> relink :: (a->STRef st a) -> (a->STRef st a) -> a -> (ST st) ()
> relink prev next a = do
>   before <- read (prev a)
>   after <- read (next a)
>   write (next before) a
>   write (prev after) a

> cover :: (Head st) -> (ST st) ()
> cover head = do
>   unlink prev next head
>   let eachDown rr = forEach right rr eachRight
>       eachRight nn = do
>         unlink up down nn
>         modify (len $ getHead nn) pred
>   forEach down (topNode head) eachDown

> uncover :: (Head st) -> (ST st) ()
> uncover head = do
>   let eachUp rr = forEach left rr eachLeft
>       eachLeft nn = do
>         modify (len $ getHead nn) succ
>         relink up down nn
>   forEach up (topNode head) eachUp
>   relink prev next head

> coverOthers :: (Node st) -> (ST st) ()
> coverOthers node = forEach right node (cover . getHead)

> uncoverOthers :: (Node st) -> (ST st) ()
> uncoverOthers node = forEach left node (uncover . getHead)

 A helper function for gdance:

> choicesToSpec :: [(Node st)] -> Spec
> choicesToSpec = Spec . (map getHint)

 This is the heart of the algorithm.  I have altered it to return only
 the first solution, or produce an error if none is found.

 Knuth used several goto links to do what is done below with tail
 recursion.

> gdance :: (Head st) -> (ST st) Spec -- [Spec]
> gdance root =
>     let
>         forward choices = do
>             maybeHead <- getBest root
>             case maybeHead of
>                 Nothing -> if null choices
>                              then error "No choices in forward" -- return [] -- for [Spec]
>                              else do -- nextSols <- recover choices -- for [Spec]
>                                      return $ (choicesToSpec choices) -- :nextSols -- for [Spec]
>                 Just head -> do cover head
>                                 startRow <- readSTRef (down (topNode head))
>                                 advance (startRow:choices)
> 
>         advance choices@(newRow:oldChoices) = do
>             let endOfRows = topNode (getHead newRow)
>             if (newRow == endOfRows)
>               then do uncover (getHead newRow)
>                       if (null oldChoices)
>                         then error "No choices in advance" -- return [] -- for [Spec]
>                         else recover oldChoices
>               else do coverOthers newRow
>                       forward choices
> 
>         recover (oldRow:oldChoices) = do
>             uncoverOthers oldRow
>             newRow <- readSTRef (down oldRow)
>             advance (newRow:oldChoices)
> 
>     in forward []


 Convert a text board into a Spec

> parseBoard :: String -> Spec
> parseBoard s = Spec (zip rcs vs'check)
>   where rcs :: [(R,C)]
>         rcs = [ (R r,C c) | r <- rng, c <- rng ]
>         isUnset c = (c=='.') || (c==' ') || (c=='0')
>         isHint c = ('1'<=c) && (c<='9')
>         cs = take 81 $ filter (\c -> isUnset c || isHint c) s
>         vs :: [V]
>         vs = map (\c -> if isUnset c then u else (V $ digitToInt c)) cs
>         vs'check = if 81==length vs then vs else error ("parse of board failed\n"++s)

 This is quite useful as a utility function which partitions the list into groups of n elements.
 Used by showSpec.

> groupTake :: Int->[a]->[[a]]
> groupTake n b = unfoldr foo b
>     where foo [] = Nothing
>           foo b = Just (splitAt n b)
 
 Make a nice 2D ascii board from the Spec (not used at the moment)

> showSpec :: Spec -> String
> showSpec spec = let pa = parse spec
>                     g = groupTake 9 (map (\(V v) -> if v == 0 then '.' else intToDigit v) $ elems pa)
>                     addV line = concat $ intersperse "|" (groupTake 3 line)
>                     addH list = concat $ intersperse ["---+---+---"] (groupTake 3 list)
>                 in unlines $ addH (map addV g)

  One line display

> showCompact spec = map (\(V v) -> intToDigit v) (elems (parse spec))

 The main routine is designed to handle the input from http://www.csse.uwa.edu.au/~gordon/sudoku17

> main = do
>   all <- getContents
>   let puzzles = zip [1..] (map parseBoard (lines all))
>   root <- stToIO initHA
>   let act :: (Int,Spec) -> IO ()
>       act (i,spec) = do
>         answer <- stToIO (do initRoot root spec 
>                              answer <- gdance (fst root) 
>                              resetRoot root
>                              return answer)
>         print (i,showCompact  answer)
>   mapM_ act puzzles

> inits' xn@(_:_) = zipWith take [0..] $ map (const xn) $ undefined:xn
> inits' _        = undefined