2016-02-13 37 views
2

我正在用SML/NJ創建一個數獨求解器。我已經掌握了實際操作輸入數據的所有功能(檢查行的合法性,強制空白空間等),但是我在回溯部分遇到問題。具有回溯的SML中的數獨求解器

我碰到過this question但我很困惑如何在SML中實現它。

。注意,板被輸入作爲表示每一行,0中的數字對於一個未知點

[[0,0,0, 2,6,0, 7,0,1], 
[6,8,0, 0,7,0, 0,9,0], 
[1,9,0, 0,0,4, 5,0,0], 

[8,2,0, 1,0,0, 0,4,0], 
[0,0,4, 6,0,2, 9,0,0], 
[0,5,0, 0,0,3, 0,2,8], 

[0,0,9, 3,0,0, 0,7,4], 
[0,4,0, 0,5,0, 0,3,6], 
[7,0,3, 0,1,8, 0,0,0]] 

這是我的(編輯)solve功能列表的列表。

exception Sudoku; 
fun solve board = 
    let fun solve' board k = 
    (* k is the row we are working on, so if it's 9, we SHOULD be done *) 
    if k = 9 then board else 
    let 
     (* get row k from the board *) 
     val row = (List.nth (board, k)); 

     fun trySpot number col = 
     (* if number >= 10, raise an exception to backtrack *) 
     if number > (length row) then raise Sudoku 
     (* if col = 9, raise an exception to backtrack *) 
     else if col = 9 then raise Sudoku 
     (* if row[col] is not a zero, move to next col *) 
     else if not (List.nth(row, col) = 0) then trySpot number (col + 1) 
     (* row doesn't contain this num already *) 
     else if length (List.filter (fn x => x = number) row) = 0 then 
      let 
      (* build the new row and board and check if legal (this works fine) *) 
      val newRow = delete(col + 1, (insertAtPos row number col)); 
      val newBoard = delete(k + 1, (insertAtPos board newRow k)); 
      val isLegal = checkLegal newBoard; 
      in 
      (* if legal, keep solving with new board as base *) 
      if isLegal then 
       solve' (force newBoard) 0 
       handle Sudoku => solve' (force board) (k + 1) 
      (* not legal, try with next num *) 
      else trySpot (number + 1) col 
      end 
     (* row already has this number, skipping *) 
     else trySpot (number + 1) col 
    in 
     (* if board is complete and legal we're done *) 
     if completedBoard board andalso checkLegal board then board 
     (* if row has a zero then try a spot *) 
     else if (zeroInList row) then trySpot 1 0 
     (* otherwise move to next row *) 
     else solve' (force board) (k + 1) 
    end 
    in 
    (* initial solve *) 
    solve' (force board) 0 
    end; 

調用上的採樣數據solve上述返回以下列出

[[4,3,5,2,6,9,7,8,1], 
[6,8,2,5,7,1,4,9,3], 
[1,9,7,8,3,4,5,6,2], 

[8,2,6,1,9,5,3,4,7], 
[3,1,4,6,8,2,9,0,0], 
[0,5,0,0,0,3,0,2,8], 

[0,0,9,3,0,0,0,7,4], 
[2,4,0,0,5,0,0,3,6], 
[7,0,3,0,1,8,0,0,0]] 

現在,這是部分正確。前四行看起來是完全正確的,這是基於我用來檢查的在線數獨求解器,但它會在第5行中搞砸。我認爲這是因爲它無法一路走回頭路。

它「備份」的唯一地方是在這條線

handle Sudoku => solve' (force board) (k + 1) 

這告訴它只是試圖解決舊板(沒有新的號碼),但是,這是防止它回溯多於一步(我認爲)。這怎麼可能完成?

如果有人好奇,想看看完整的代碼,你可以找到它here.

提前感謝!

+1

Hansen和Rischel編寫的書籍「使用SML進行編程」介紹了一個回溯算法的例子,用於解決使用異常實現的8皇后問題。我能夠修改他們的代碼來獲得騎士旅程,而沒有太多困難。它可能會給你一些想法(雖然現在我更可能使用選項而不是例外)。 –

+0

感謝您的建議,我會看看 –

+0

@JohnColeman,我看了他們的實現,並試圖修改我的求解函數來實現帶有異常的回溯。它在某種程度上起作用,但我不確定如何讓它進一步回到決策樹中。有任何想法嗎? –

回答

1

我似乎已經得到了我的解決函數來處理我嘗試過的所有案例。訣竅是跟蹤我們正在工作的地點的非法數字列表。求解器現在跳過這些數字,並且如果它找不到前進的路徑則回溯。

exception Sudoku; 
(* solves a soduku board input that gives a list of lists representing the rows of a board (0 for unknown spaces) *) 
fun solve board = 
    let fun solve' board row illegalNums = 
    (* if we are on row 9 and board is complete/legal, we are done *) 
    if row = 9 andalso completedBoard board andalso checkLegal board then board else 
    (* if we are on row 9 but the board is not complete/legal, throw an exception to backtrack *) 
    if row = 9 then raise Sudoku else 
    let 
     (* get the current row we are working on *) 
     val cRow = (List.nth (board, row)); 

     (* trys a row[col] on the board with a certain number *) 
     fun trySpot num cRow col = 
     (* if number >= 10, raise an exception to backtrack *) 
     if num > 9 then raise Sudoku 
     (* if row[col] is not a 0, try next col *) 
     else if not (List.nth (cRow, col) = 0) then trySpot num cRow (col + 1) 
     (* if we know that the number we are on isn't allowed, skip to next number *) 
     else if length (List.filter (fn x=> x = num) illegalNums) > 0 then trySpot (num + 1) cRow col 
     (* if row already has this number, skip to next number *) 
     else if length (List.filter (fn x=> x = num) cRow) > 0 then trySpot (num + 1) cRow col 
     (* if col already has this number, skip to next number *) 
     else if length (List.filter (fn x=> x = num) (List.nth((rowsToCols board), col))) > 0 then trySpot (num + 1) cRow col 
     else 
      let 
      (* make our new row and board *) 
      val newRow = delete(col + 1, (insertAtPos cRow num col)); 
      val newBoard = delete(row + 1, (insertAtPos board newRow row)); 
      in 
      (* if new board is legal, continue solving *) 
      if checkLegal newBoard then 
       solve' (force newBoard) 0 [] 
       (* handle any exceptions by adding the number to our list of illegal numbers, thereby backtracking *) 
       handle Sudoku => solve' (force board) row ([email protected][num]) 
      (* if new board is not legal, try next number *) 
      else trySpot (num + 1) cRow col 
      end 
    in 
     (* if board is completed and legal, return board *) 
     if completedBoard board andalso checkLegal board then board 
     (* if current row has at least one 0, try a number in that row (beginning at col 1) *) 
     else if (zeroInList cRow) then trySpot 1 cRow 0 
     (* else, skip to next row and solve *) 
     else solve' (force board) (row + 1) [] 
    end 
    in 
    (* initial solve *) 
    solve' (force board) 0 [] 
    end; 

較硬板可能需要相當長一段時間完全解決,但每一個我最終測試到達那裏。我確信在我的代碼中可以做很多優化,所以我願意提供建議。

+1

我很高興你能夠正常工作。也許你可以在Code Review上發佈它以獲得更多反饋。我認爲有一個改進是爲了擺脫例外情況而選擇。返回'NONE'代替引發一個異常,模式匹配'NONE'替換'handle'。我在O'CAML中讀到,至少使用選項比提出異常要快得多,因爲它不涉及展開調用堆棧。看到這個問題:http://stackoverflow.com/q/7952625/4996248 –

+1

再讀一遍,我不確定是否會有一個時間增益使用選項,但我仍然認爲這些選項在概念上比例外更清楚。有一點可能會有所作爲,就是用'vectors'來代替列表 - 爲了你的棋盤表示 - 你的棋盤有固定的大小,所以你不需要列表增長的能力,而'O(1)'訪問時間爲載體應該可能是一種收益。 –

+0

@JohnColeman感謝您的建議。我會看看選項而不是例外。不幸的是,我堅持列表。這是一堂課的作業,我們的教授希望我們使用普通的舊名單。我仍然可能會弄亂周圍,看看載體是否會有所改進,所以感謝這個想法! –