跳转至内容

XQuery/数独

来自维基教科书,开放书籍,开放世界

XQuery 中的数独求解器

数独谜题可以用矩阵形式表示。以下是泰晤士报数独书中的一个谜题的一部分。

<?xml version="1.0" encoding="UTF-8"?>
<sudoku name="Times 1 p1">
    <matrix>
        <row>
            <col/>
            <col>6</col>
            <col>1</col>
            <col/>
            <col>3</col>
            <col/>
            <col/>
            <col>2</col>
            <col/>
        </row>
        <row>
            <col/>
            <col>5</col>
            <col/>
            <col/>
            <col/>
            <col>8</col>
            <col>1</col>
            <col/>
            <col>7</col>
        </row>
        <row>
            <col/>

主脚本

[编辑 | 编辑源代码]

主脚本传递一个引用问题 XML 文件的 URL。矩阵格式转换为单元格序列,解决谜题,将结果单元格列表转换回矩阵,并打印矩阵。求解搜索的经过时间在初始问题和解决方案之后计算并显示。

import module namespace su = 'http://www.cems.uwe.ac.uk/wiki/sudoku' at 'sudoku4.xqm';

declare option exist:serialize 'method=xhtml  media-type=text/html';

declare function local:duration-as-ms($t) {
      round((minutes-from-duration($t) * 60 + seconds-from-duration($t)) * 1000 )
};

let $url := request:get-parameter('url',())
let $sudoku :=doc($url)/sudoku
let $p := $sudoku/matrix
let $pc :=  su:matrix-to-cells($p)
let $start := util:system-time()
let $ps := su:solve($pc)  
let $finish := util:system-time()
let $elapsedms := local:duration-as-ms($finish - $start)
let $s := su:cells-to-matrix($ps)

return 
<div>
  <h1>Solving Sudoku problem {string($sudoku/@name)}</h1>
  <table border = '1'>
      <tr>
        <td>{su:matrix-to-table($p)}</td>
        <td>{su:matrix-to-table($s)}</td>
       </tr>
   </table>
   <p>Elapsed time in milliseconds : {$elapsedms}</p>
 </div>

此模块定义了支持对解决方案树进行暴力深度优先搜索的必要函数。这里使用了两种数独谜题的表示方式

nested columns within rows  -  element(matrix) - the input format
list of cells with explicit row and column numbers  - element(cells)

算法从单元格列表表示开始。计算每个空方格可能的解决方案数量。如果有一个单元格只有一个值,则将该单元格添加到单元格列表中,算法继续。如果一个单元格有多个可能的值,算法将遍历这些可能的值,假设每个值依次是正确的值。如果没有可能的值,则该部分解决方案是不可行的,该解决方案路径将被放弃,返回 null,并将尝试下一个可能的单元格值。

declare function su:matrix-to-table($s as element(matrix)) as element(table) {
<table class="sudoku">
    { for $r in $s/row
      return
       <tr>
          { for $c in $r/col
            return <td>{string($c)}</td>
          }
      </tr>
    }
</table>
};

declare function su:matrix-to-cells($s as element(matrix)) as element(cell)* {
 for $i in (1 to 9)
   for $j in (1 to 9)
   let $c := $s/row[$i]/col[$j]
   return
      if ($c/text())
      then <cell row='{$i}' col='{$j}'>{string($c)}</cell>
      else ()
};

declare function su:cells-to-matrix($s as element(cell)*) as element(matrix) {
<matrix>
  { for $i in (1 to 9)
    return
    <row>
     { for $j in (1 to 9)
       let $c := $s[@row = $i][@col = $j]
        return
          <col>{string($c)}</col>
      }
   </row>
  }
</matrix>
};

declare function su:block($s as element(cell)*, $i as xs:integer, $j as xs:integer ) as element(cell)+ {
(: return the block of 9 cells containing $i, $j :)
   let $tci := (($i - 1) idiv 3 * 3 ) + 1
   let $tcj := (($j - 1) idiv 3 * 3 ) + 1
   return $s[@row = ($tci to $tci + 2)][@col = ($tcj to $tcj + 2)]
};

declare function su:row($s as element(cell)*,$i as xs:integer) as element(cell)+ {
(:  return the cells in row $i :)
   $s[@row = $i]
};

declare function su:col($s as element(cell)* ,$j as xs:integer) as element(cell)+{
(: return the cells in column $j :)
   $s[@col = $j]
};

declare function su:values($s as element(cell)*, $i as xs:integer, $j as xs:integer) as xs:integer* {
(: return the set (sequence) of values in a cell's row, column and block :)
   distinct-values( (su:row($s,$i) ,su:col($s,$j) , su:block($s,$i,$j) ))
};

declare function su:missing-values($s as element(cell)*,$i as xs:integer,$j as xs:integer) as xs:integer* {
(: return the numbers missing from 1 to 9 i.e. the possible values for cell $i , $j :) 
   let $vals := su:values($s,$i,$j)
   return 
     (1 to 9) [not(. = $vals)]
};

declare function su:missing-cells($s as element(cell)*) as element(cells)* {
   for $i in (1 to 9)
   for $j in (1 to 9)
   where empty($s[@row = $i][@col = $j])
   return
     let $m := su:missing-values($s,$i,$j)
     return <cell row='{$i}' col='{$j}' n='{count($m)}'>{$m}</cell>
};

declare function su:best-cell($s as element(cell)*) as element(cell)* {
(: return (one of ) the cells with the minimum number of possible values :)
   let $empty :=  su:missing-cells($s)
   let $min := min( $empty/@n)
   return 
      ($empty[@n = $min])[1]
};

declare function su:search-for-solution($s as element(cell)*, $cell as element(cell), $posvalues as xs:string*) {
(: recursive search of a set of possible values for a cell :)
  if (empty($posvalues))
  then ()  
  else 
     let $pos:= $posvalues[1]   (: choose the first :) 
     let $posit := <cell row='{$cell/@row}' col='{$cell/@col}'>{$pos}</cell>
     let $sol := su:solve(($s,$posit)) (: try with this posited value for the cell :)
     return 
       if ($sol )  (: a solution :)
       then $sol
       else   (: continue with the rest of the possible values :)
             su:search-for-solution($s, $cell, subsequence($posvalues,2))
};

declare function su:solve($s as element(cell)*) as element(cell)* {
(:  solve a sudoku problem  - $s is  a sequence of cells with values :)
   let $cell:= su:best-cell($s)
   return
      if (empty($cell) )
      then $s  (: solved :)
      else if ( $cell/@n=0)  (: infeasible :)
      then ()
      else if ($cell/@n = 1)  (: forced move :)
      then su:solve(($s,$cell))
      else   (: multiple possible, so do depth-first search  :)
         su:search-for-solution($s, $cell, tokenize($cell, ' ' ))
};

使用泰晤士报数独书中的几个问题

此代码需要 eXist 1.3 或更高版本才能运行。

华夏公益教科书