用Answer Set Programming解数独

落爺英雄遲暮 提交于 2020-08-19 23:27:27

搜索“ASP solver”可以得到若干个,clasp是其中一个http://www.cs.uni-potsdam.de/clasp/?page=main

有本教程叫A User's Guide to gringo, clasp, clingo, and iclingo,Google一下就可以下到

Answer Set Programming的优点在于,你只需要定义规则,求解的方法由ASP solver来解决,这是一种declarative language,和SQL同类

 看如下这个数独(http://www.sudoku.name/index-cn.php):

首先描述facts(文件sudoku_fact.lp):

row(1..9).
col(1..9).
value(1..9).
put(1,2,9).
put(1,4,7).
put(1,6,6).
put(1,7,1).

put(2,2,2).
put(2,3,5).
put(2,4,3).
put(2,8,6).
put(2,9,7).

put(3,3,6).
put(3,5,2).
put(3,8,8).

put(4,1,5).
put(4,6,9).
put(4,7,3).

put(5,1,7).
put(5,3,3).
put(5,5,4).
put(5,7,2).
put(5,9,8).

put(6,3,8).
put(6,5,3).
put(6,6,7).
put(6,9,6).

put(7,2,8).
put(7,5,5).
put(7,7,6).

put(8,1,4).
put(8,2,3).
put(8,6,8).
put(8,7,7).
put(8,8,2).

put(9,3,9).
put(9,4,1).
put(9,6,2).
put(9,8,3).
View Code

其中put(x,y,z)表示第x行第y列的值为z;row(1..9).表示row的取值为1到9,对col和value类似

然后描述规则(文件sudoku_enc.lp):

%Default,%开头的是注释
#const n = 9.
%Generate
1 { put(X,Y,1..n) } 1 :- row(X),col(Y).
%Test
:- put(A,B,C), put(A,D,C), B != D.
:- put(A,B,C), put(D,B,C), A != D.
:- put(A,B,C), put(D,E,C), (A-1) #div 3 == (D-1) #div 3, (B-1) #div 3 == (E-1) #div 3, A != D, B != E.

去掉3行注释,再去掉定义n=9这个常量,实际只有4行,每一行都是一条规则(rule)

第1行表示:第X行第Y列只能放一个值——显然,每个空格只能填一个值。

{ put(X,Y,1..n) }是{ put(X,Y,1) , put(X,Y,2), ... , put(X,Y,n)}的简写,叫语法糖(syntax sugar),就是为了方便而存在的东西, 这个式子首尾的两个1分别表示{}中成立的文字(literal)的个数的下界和上界,这里上界和下界相同,表示只有一个文字成立。

":-"表示右边可以推出左边,即如果右边所有文字(literal)都为真,则左边必为真。":-"左边部分称为规则的头(head),右边称为规则的体(body),规则体之间的逗号可以看成“&&”,或者合取

row(X)表示X在fact所定义的0到9之间,col类似

 

第2行表示:同一行的不同位置不能放相同的数字

“:-”作为开头,即规则的头为空,后面跟的是需要排除的情况,也称为约束(constraint)

 

第3行表示:同一列的不同位置不能放相同的数字

第4行表示:同一个3×3的小九宫格的不同位置不能放相同的数字

#div表示整数除法,例如1 #div 3 = 0, 2 #div 3 = 0, 3 #div 3 = 1,就是C语言中的整数除法

所以第1个九宫格(行号-1)#div 3的结果是0,(列号-1)#div 3的结果也是0,这两个值组成数对(0,0)唯一标识了这个小九宫格,这是一个小技巧,使得我们这里的规则可以写得简洁到只需要一句话。

调用iclingo.exe来求解:iclingo.exe sudoku_fact.lp sudoku_enc.lp

最终结果是:

3  9  4  7  8  6  1  5  2
8  2  5  3  1  4  9  6  7
1  7  6  9  2  5  4  8  3
5  4  2  8  6  9  3  7  1
7  6  3  5  4  1  2  9  8
9  1  8  2  3  7  5  4  6
2  8  7  4  5  3  6  1  9
4  3  1  6  9  8  7  2  5
6  5  9  1  7  2  8  3  4

和标准答案一致

从iclingo的输出还可以知道,这是唯一解。

此外,为了格式化输出,这里写了一个格式化小程序format.cpp,一并贴上来

#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
int x[10][10];
int main(int argc, const char *argv[])
{
    int a, b, c;
    string s;
    //while (scanf("put(%d,%d,%d) ", &a, &b, &c) == 3) {
    while(cin >> s) {//detect 'put'
        if (s[0] == 'p') {
            x[s[4] - '0'][s[6] - '0'] = s[8] - '0';
        }
    }
    for (int i = 1; i < 10; i++) {
        for (int j = 1; j < 10; j++) {
            printf("%d  ", x[i][j]);
        }
        printf("\n");
    }
    return 0;
}

用的时候可以借助管道:iclingo.exe sudoku_fact.lp sudoku_enc.lp | format.exe

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!