了解N皇后问题的CLP(FD)Prolog代码

时间:2018-11-21 06:28:22

标签: prolog clpfd n-queens

我正在尝试理解N-queens问题的解决方案,如下所示:

:- use_module(library(clpfd)).

n_queens(N, Qs) :-
    length(Qs, N),
    Qs ins 1..N,
    safe_queens(Qs).

safe_queens([]).
safe_queens([Q|Qs]) :-
    safe_queens(Qs, Q, 1),
    safe_queens(Qs).

safe_queens([], _, _).
safe_queens([Q|Qs], Q0, D0) :-
    Q0 #\= Q,
    abs(Q0 - Q) #\= D0,
    D1 #= D0 + 1,
    safe_queens(Qs, Q0, D1).

我无法理解以下代码段:

safe_queens([]).
safe_queens([Q|Qs]) :-
    safe_queens(Qs, Q, 1),
    safe_queens(Qs).

safe_queens([], _, _).
safe_queens([Q|Qs], Q0, D0) :-
    Q0 #\= Q,
    abs(Q0 - Q) #\= D0,
    D1 #= D0 + 1,
    safe_queens(Qs, Q0, D1).

请帮助我理解。任何帮助将不胜感激。

1 个答案:

答案 0 :(得分:10)

由于您没有提供任何示例查询,因此请从一些示例查询开始以确定参数和输出格式。 通常,要确定未知代码的参数和输出格式,需要查看代码以了解参数的结构,然后尝试执行示例查询。另外请注意,此代码使用Constraint Logic Programmingclpfd;当我读到它时,我实际上不再思考syntactic unification,而开始思考constraints。我将其视为Prolog中嵌入的独立系统,而不是其他谓词。您会注意到,在此答案中,constraint经常被使用,而predicaterule却很少使用,即使这是Prolog。

由于N-Queens问题众所周知是逻辑问题,因此Google的快速搜索(clpfd n queens)出现了SWI-Prolog Example: Eight queens puzzle。注意,添加关键字clpfd对于理解代码的这种变化至关重要。其他编程语言中有many个解决方案。

这给出了一个示例查询n_queens(8, Qs), label(Qs),其中label/1返回系统生成的变量的值。 这也告诉我们第一个参数是一个正整数,第二个参数是第一个参数的长度列表。 同样,通过解决此问题,第一个参数是木板的尺寸,因此11x1木板,88x8木板,依此类推。 ,以及将要出现的皇后人数。
接下来会帮助您了解有效的解决方案是什么,或者至少对于一组参数而言是有效的解决方案。

Eight queens puzzle的Wikipedia文章在counting solutions部分提供了该文章。 这表明,对于1x1的板,有一个解决方案,对于2x2或3x3的板,没有解决方案,对于4x4的板,有两个解决方案,依此类推。

对于1x1板,有一种解决方案。

?- n_queens(1,Qs),label(Qs).
Qs = [1].

对于2x2板,没有解决方案。

?- n_queens(2,Qs),label(Qs).
false.

对于4x4板,有两种解决方案。

?- n_queens(4,Qs),label(Qs).
Qs = [2, 4, 1, 3] ;
Qs = [3, 1, 4, 2] ;
false.


Qs = [2, 4, 1, 3]

enter image description here

要解释结果,列表中的位置与板上的列相对应,值与板上的一行相对应,因此对于列表中的第一个值(2),其读为{{1} },对于列表(a queen in row 2, column 1)中的第二个值,它显示为4

a queen in row 4, column 2

enter image description here

注意:使用Chess Diagram Setup

生成的图像

如果我们使用值作为变量来运行查询,则结果将是有效答案的无休止游行。

Qs = [3, 1, 4, 2]

现在我们知道代码已运行并提供了有效的解决方案,我们可以开始对其进行剖析。
通常,以?- n_queens(N,Qs),label(Qs). N = 0, Qs = [] ; N = 1, Qs = [1] ; N = 4, Qs = [2, 4, 1, 3] ; N = 4, Qs = [3, 1, 4, 2] ; N = 5, Qs = [1, 3, 5, 2, 4] ; N = 5, Qs = [1, 4, 2, 5, 3] ; N = 5, Qs = [2, 4, 1, 3, 5] ; N = 5, Qs = [2, 5, 3, 1, 4] ; N = 5, Qs = [3, 1, 4, 2, 5] ; N = 5, Qs = [3, 5, 2, 4, 1] ; N = 5, Qs = [4, 1, 3, 5, 2] ... 开头的SWI-Prolog trace/0或SWI-PRolog GUI-tracer会是一种选择的工具,但是在clpfd上使用过它,才知道我不是{ {3}}。尝试一下,您会明白为什么。

剖析。

gtrace/0

这很有趣。
为了使这一点更容易理解,请将系统生成的变量替换为用户友好的变量,并人工理解该语句的含义。

?- n_queens(1,Qs).
Qs = [1].

?- n_queens(2,Qs).
Qs = [_1942, _1948],
_1942 in 1..2,
abs(_1942-_1948)#\=1,
_1942#\=_1948,
_1948 in 1..2.

请注意,对于其中带有?- n_queens(2,Qs). Qs = [A, B], A in 1..2, abs(A-B)#\=1, A#\=B, B in 1..2. 的CLP(FD)运算符,通常是约束条件,例如Constraint Logic Programming#\=的读取方式类似于普通运算符,而不是#

#

因此,这些只是一组约束。如果您尝试手动解决约束,则会发现没有解决方案,例如

`A in 1..2`    reads the value for `A` must be in the range `1..2`
`abs(A-B)#\=1` reads the difference of the values between `A` and `B` must not equal 1
`A#\=B`        reads the value of `A` must not equal the value of `B`
`B in 1..2`    reads the value of `B` must be in `1..2`

对4x4板进行相同操作

0,_  invalid by `A in 1..2`
_,0  invalid by `B in 1..2`
3,_  invalid by `A in 1..2`
_,3  invalid by `B in 1..2`
1,1  invalid by `A#\=B` 
1,2  invalid by `abs(A-B)#\=1`
2,1  invalid by `abs(A-B)#\=1`
2,2  invalid by `A#\=B` 


?- n_queens(4,Qs).
Qs = [_5398, _5404, _5410, _5416],
_5398 in 1..4,
abs(_5398-_5416)#\=3,
_5398#\=_5416,
abs(_5398-_5410)#\=2,
_5398#\=_5410,
abs(_5398-_5404)#\=1,
_5398#\=_5404,
_5416 in 1..4,
abs(_5410-_5416)#\=1,
_5410#\=_5416,
abs(_5404-_5416)#\=2,
_5404#\=_5416,
_5410 in 1..4,
abs(_5404-_5410)#\=1,
_5404#\=_5410,
_5404 in 1..4.

要接受一点,但这是逻辑,我们可以重新排列语句,其含义将相同。

因此将类似的语句分组,按变量排序,然后按简单性对组进行排序

?- n_queens(4,Qs).
Qs = [A, B, C, D],
A in 1..4,     reads the value for `A` must be in the range `1..4`
abs(A-D)#\=3,  reads the difference of the values between `A` and `D` must not equal 3
A#\=D,         reads the value of `A` must not equal the value of `D`
abs(A-C)#\=2,  reads the difference of the values between `A` and `C` must not equal 2
A#\=C,         reads the value of `A` must not equal the value of `C`
abs(A-B)#\=1,  reads the difference of the values between `A` and `B` must not equal 1
A#\=B,         reads the value of `A` must not equal the value of `B`
D in 1..4,     reads the value for `D` must be in the range `1..4`
abs(C-D)#\=1,  reads the difference of the values between `C` and `D` must not equal 1
C#\=D,         reads the value of `C` must not equal the value of `D`
abs(B-D)#\=2,  reads the difference of the values between `B` and `D` must not equal 2
B#\=D,         reads the value of `B` must not equal the value of `D`
C in 1..4,     reads the value for `C` must be in the range `1..4`
abs(B-C)#\=1,  reads the difference of the values between `B` and `C` must not equal 1
B#\=C,         reads the value of `B` must not equal the value of `C`
B in 1..4.     reads the value for `B` must be in the range `1..4`

现在解释约束条件并显示它们与方形板上的皇后有何关系;请注意,我说的是方形板,而不是国际象棋板,因为国际象棋板是8x8,并且此代码适用于不同尺寸的方形板。


`A in 1..4` reads the value for `A` must be in the range `1..4` `B in 1..4` reads the value for `B` must be in the range `1..4` `D in 1..4` reads the value for `D` must be in the range `1..4` `C in 1..4` reads the value for `C` must be in the range `1..4` `A#\=B` reads the value of `A` must not equal the value of `B` `A#\=C` reads the value of `A` must not equal the value of `C` `A#\=D` reads the value of `A` must not equal the value of `D` `B#\=C` reads the value of `B` must not equal the value of `C` `B#\=D` reads the value of `B` must not equal the value of `D` `C#\=D` reads the value of `C` must not equal the value of `D` `abs(A-B)#\=1` reads the difference of the values between `A` and `B` must not equal 1 `abs(A-C)#\=2` reads the difference of the values between `A` and `C` must not equal 2 `abs(A-D)#\=3` reads the difference of the values between `A` and `D` must not equal 3 `abs(B-C)#\=1` reads the difference of the values between `B` and `C` must not equal 1 `abs(B-D)#\=2` reads the difference of the values between `B` and `D` must not equal 2 `abs(C-D)#\=1` reads the difference of the values between `C` and `D` must not equal 1

意味着A in 1..4皇后必须放置在4x4板上的位置。在处理约束问题时,您通常会发现我们作为人类所想当然的东西或对常识的思考需要作为特定的约束给出,以防万一。还学习添加常识规则有时是创建AI解决方案时最困难的任务之一。虽然找不到参考文献,但是#=的创建者在添加规则时,时间的概念花了很多时间才能正确(没有双关语)。其余A这样的约束条件只是确保没有皇后被放置在棋盘外。


A in 1..4

要更好地理解此约束,请按照约束定义,以4x4板和白色皇后为有效位置,将黑色皇后为无效位置的图片制作。

Cyc

因此,A#\=B是第1行中的白色皇后,A是第1行中的黑色皇后。由于A不能等于B,所以这说明如果女王B位于第1行中那么皇后A不能在第1行中。由于该规则与变量一起使用,这意味着B皇后在A中的任何行都不能在该行中。其余B这样的约束条件只是确保没有两个皇后可以在同一行中。

将此约束视为女王的水平进攻。


A#\=B

要更好地理解此约束,请按照约束定义,以4x4板和白色皇后为有效位置,将黑色皇后为无效位置的图片制作。

abs(A-B)#\=1 A有四个位置,但是由于该规则是水平对称的(1等于4,而2等于3),所以我只会做两个。

1,2,3,4为1时。

enter image description here

由于A为1,所以A不能为2。

B

1-2 = -1 ABS(-1) = 1 1 can not equal 1. 为2时。

enter image description here

由于A为2,所以A不能为1。

B

由于2 - 1 = 1 ABS(1) = 1 1 can not equal 1. 是2,所以A不能是3。

B

如果检查了使用皇后2 - 3 = -1 ABS(-1) = 1 1 can not equal 1. 和皇后A的约束

D

abs(A-D)#\=3为1时。

enter image description here

由于A为1,所以A不能为4。

D

1-4 = -3 ABS(-3) = 3 3 can not equal 1. 为2时。

由于A为2,因此A可以为D

1

由于2-1 = 1 ABS(1) = 1 1 can not equal 3. 为2,因此A可以为D

2

由于2-2 = 0 ABS(0) = 0 0 can not equal 3. 为2,因此A可以为D

3

由于2-3 = -1 ABS(-1) = 1 1 can not equal 3. 为2,因此A可以为D

4

将此约束视为女王的对角线攻击。


但是等一下,女王可以水平,垂直和对角移动,垂直移动的约束在哪里?

尽管这在示例查询的输出中未显示为约束,但存在约束。到目前为止,我们有一些限制条件,将皇后区的位置限制为在板上,水平进攻和对角线进攻是不同的约束,但是数据的结构,长度为N的列表也是一个约束,({{ 1}}),并将2-4 = -2 ABS(-2) = 2 2 can not equal 3. 皇后限制到第一列,将[A,B,C,D]皇后限制到第二列,依此类推。同样,这是学习AI编码的要点之一,就是我们作为人类的思维方式并不总是直接转化为如何用计算机解决问题的方式。因此,尽管此代码使用约束来解决问题,但同时也使用了数据结构。

将列表视为女王的纵队攻击。

在同一列中不能有两个女王/王后,这受标量变量中不能有两个值的限制。

enter image description here


在这一点上,你们中的许多人都将代码的其余部分识别为辅助和递归谓词A和递归谓词B


safe_queens/1

这是处理列表的标准递归调用,例如

safe_queens/3

这两个语句

safe_queens([], _, _).
safe_queens([Q|Qs], Q0, D0) :-
    Q0 #\= Q,
    abs(Q0 - Q) #\= D0,
    D1 #= D0 + 1,
    safe_queens(Qs, Q0, D1).

上面已解释

safe_queens([], _, _).
safe_queens([H|T], _, _) :-
    % Process head of list (H)
    safe_queens(T, _, _). % Process tail of list (T)

Q0 #\= Q abs(Q0 - Q) #\= D0 设置为D1 #= D0 + 1

如果我们这样修改谓词

D1

并运行这些查询,我们看到它生成了一些约束

D0 + 1

permutations([], _, _).
permutations([Q|Qs], Q0, D0) :-
    write(Q0),write('#\\='),writeln(Q),
    write('abs('),write(Q0),write('-'),write(Q),write(')#\\='),writeln(D0),
    D1 is D0 + 1,
    permutations(Qs, Q0, D1).

这是处理列表的标准递归调用,例如

?- permutations(['B','C','D'],'A',1).
A#\=B
abs(A-B)#\=1
A#\=C
abs(A-C)#\=2
A#\=D
abs(A-D)#\=3
true.

?- permutations(['C','D'],'B',1).
B#\=C
abs(B-C)#\=1
B#\=D
abs(B-D)#\=2
true.

?- permutations(['D'],'C',1).
C#\=D
abs(C-D)#\=1
true.

以及safe_queens([]). safe_queens([Q|Qs]) :- safe_queens(Qs, Q, 1), safe_queens(Qs). 的帮助者,因为该语句

safe_queens([]).
safe_queens([H|T]) :-
    % Process head of list (H)
    safe_queens(T). % Process tail of list (T)

safe_queens/3的第三个参数初始化为 safe_queens(Qs, Q, 1)

如果我们这样修改谓词

safe_queens/3

并运行此查询,我们看到它生成了1

所需的参数
generate_args([]).
generate_args([Q|Qs]) :-
    write('Qs: '),write(Qs),write(', Q: '),write(Q),writeln(', 1'),
    generate_args(Qs).

但是在您的问题中,您没有询问第一个谓词

safe_queens/3

具有

?- generate_args(['A','B','C','D']).
Qs: [B,C,D], Q: A, 1
Qs: [C,D], Q: B, 1
Qs: [D], Q: C, 1
Qs: [], Q: D, 1
true.

生成具有未绑定变量的长度N的列表

n_queens(N, Qs) :-
    length(Qs, N),
    Qs ins 1..N,
    safe_queens(Qs).

enter image description here

并且具有关键的约束条件声明

length(Qs,N)

生成类似

的约束
[A,B,C,D]

enter image description here


现在将关键差异附加到查询中

Qs ins 1..N

如果您使用SWI-Prolog GUI跟踪器并运行代码直到A in 1..4 的末尾,您将在调试器中看到约束列表,但没有解决方案

enter image description here

那是因为那些谓词生成内部维护的约束,直到调用labels(Qs) 才解决约束以生成结果。