今天来看几个详细的例子,看看Prolog怎么解决具体问题的。
1 解决数独问题
首先我们要明白Prolog的解题思路:我们只需要提供游戏规则即可。游戏规则如下:
valid([]).
valid([Head|Tail]):-fd_all_different(Head),valid(Tail).
- 对于一个已经解决了的难题,难题中的数字与解决方法中的数字应该相同。
- 数独的题板是一个有着16个单元的网格,填充值从1~4。
- 题板有4行、4列以及4*4个格子。
- 如果每行每列里的格子都没有重复数字,那么这个难题就被解决了。
让我们从第一条开始看。解决方法和难题中的数字应该匹配:
sudoku(Puzzle,Solution):-Solution=Puzzle.
但是这样有个问题,如下是无效的:
|?-sudoku([1,2,3],Solution).
Solution=[1,2,3]
yes
显然我们还应该有16个元素的限制。同时,它填充的数字应该在1~4之间。GNU Prolog使用内置的被称作 fd_domain(List,LowerBound,UpperBound)的谓词来表达合法值。如果列表中的 所有元素都在LowerBound和UpperBound之间,包括UpperBound(也包括LowerBound),那么这个谓词为真。
sudoku(Puzzle,Solution):-Solution=Puzzle,Puzzle=[S11,S12,S13,S14,
S21,S22,S23,S24,
S31,S32,S33,S34,
S41,S42,S43,S44],
fd_domain(Puzzle,1,4).
接下来我们要将难题分隔成行、列和格子。
Row1=[S11,S12,S13,S14], Row2=[S21,S22,S23,S24], Row3=[S31,S32,S33,S34], Row4=[S41,S42,S43,S44],
Col1=[S11,S21,S31,S41], Col2=[S12,S22,S32,S42], Col3=[S13,S23,S33,S43], Col4=[S14,S24,S34,S44],
Square1=[S11,S12,S21,S22], Square2=[S13,S14,S23,S24], Square3=[S31,S32,S41,S42], Square4=[S33,S34,S43,S44].
注意,这里的Square表示格子,即每个格子(4个元素组成)的元素也各不相同。
如果所有行和列和格子都没有重复的元素,那么就是有效地。我们将使用一个GNU Prolog谓词做重复元素的检查。
如果所有在List中的元素都不同,那么fd_all_different(List)返回真。
valid([]). valid([Head|Tail]):-fd_all_different(Head),valid(Tail).第一句表达的是一个空列表是有效的。第二句的意思是,如果第一个元素列表的各项都不相同,并且剩余列表都是有效的,那么这个列表就是有效地。(注意,这里的变量Head可以表示一个List)
这样,我们就完成了这个算法,测试结果如下:
|?-sudoku([_,_,2,3,
_,_,_,_,
_,_,_,_,
3,4,_,_],
Solution).
Solution=[4,1,2,3,2,3,4,1,1,2,3,4,3,4,1,2]
下面是全部代码:
valid([]). valid([Head|Tail]):-fd_all_different(Head),valid(Tail).
sudoku(Puzzle,Solution):-Solution=Puzzle,Puzzle=[S11,S12,S13,S14, S21,S22,S23,S24, S31,S32,S33,S34, S41,S42,S43,S44], fd_domain(Puzzle,1,4),Row1=[S11,S12,S13,S14], Row2=[S21,S22,S23,S24], Row3=[S31,S32,S33,S34], Row4=[S41,S42,S43,S44],Col1=[S11,S21,S31,S41], Col2=[S12,S22,S32,S42], Col3=[S13,S23,S33,S43], Col4=[S14,S24,S34,S44],Square1=[S11,S12,S21,S22], Square2=[S13,S14,S23,S24], Square3=[S31,S32,S41,S42], Square4=[S33,S34,S43,S44],
valid([Row1,Row2,Row3,Row4,
Col1,Col2,Col3,Col4,
Square1,Square2,Square3,Square4]).
重点是我们自己构建规则,然后电脑会通过规则自己去匹配。
2 八皇后问题
首先看一下我们的游戏规则:
- 一个棋盘有八个皇后
- 每个皇后有一个行号和列号,行号和列号的取值范围都是1~8
- 任意两个皇后不可以共享一行
- 任意两个皇后不可以共享一列
- 任意两个皇后不可以共享一个对角线(西南到东北)
- 任意两个皇后不可以共享一个对角线(西北到东南)
让我们从第一个规则开始。一个棋盘有八个皇后,意味着我们的列表大小必须是8。我们可以使用前面介绍过的
count谓词,也可以使用Prolog内置的
length谓词。
如果List有N个元素,那么length(List,N)将返回真。
eight_queens(List):-length(List,8).
接下来,我们要确保列表中的皇后是有效的。
valid_queen((Row,Col)):-Range=[1,2,3,4,5,6,7,8],member(Row,Range),member(Col,Range).
谓词member的意思是第一个参数在第二个参数指定的范围内。
接下来的规则检查整个棋盘是否是由有效地皇后组成的。
valid_board([]).
valid_board([Head|Tail]):-valid_queen(Head),valid_board(Tail).
继续下一个规则:两个皇后不能共享同一行。怎么定义行?编写一个函数rows(Queens,Rows),如果Rows是由所有皇后的Row元素组成的列表,那么函数为真。如下:
rows([],[]).
rows([(Row,_)|QueenTail],[Row|RowsTail]):-rows(QueensTail,RowsTail).
这里很好理解,一个空Queens列表执行rows的结果是一个空Rows列表。如果Queens列表中第一个元素的Row与Rows列表中的第一个元素匹配,并且如果对Queens的Tail列表执行rows的结果是Rows的Tail列表,那么rows(Queens,Rows)的结果就是Rows列表。( 这段可以这么理解,row([(1,2),(1,5),(3,6),(5,6),(4,8),(7,6),(2,5),(3,4)],A)将第一个列表中的行号取出来,给A)
取列号的规则是类似的:
cols([],[]).
cols([(_,Col)|QueensTail],[Col|ColsTail]):-cols(QueensTail,ColsTail).
diags1([],[]).
diags1([(Row,Col)|QueensTail],[Diagonal|DiagonalsTail]):-Diagonal is Col-Row,diags1(QueensTail,DiagonalsTail).
这个规则也类似于rows,不过多了一个限制:Diagonal is Col-Row。 注意这不是合一,而是一个is谓词。(注意,在左上角到右下角的对角线上的元素,Col-Row的值是相同的)右上到左下的元素,作如下处理:
diags2([],[]).
diags2([(Row,Col)|QueensTail],[Diagonal|DiagonalsTail]):-Diagonal is Col+Row,diags2(QueensTail,DiagonalsTail).
下面是完整的解决代码:
valid_queen((Row,Col)):-Range=[1,2,3,4,5,6,7,8],member(Row,Range),member(Col,Range).
valid_board([]). valid_board([Head|Tail]):-valid_queen(Head),valid_board(Tail).
rows([],[]). rows([(Row,_)|QueenTail],[Row|RowsTail]):-rows(QueensTail,RowsTail).
cols([],[]). cols([(_,Col)|QueensTail],[Col|ColsTail]):-cols(QueensTail,ColsTail).
diags1([],[]). diags1([(Row,Col)|QueensTail],[Diagonal|DiagonalsTail]):-Diagonal is Col-Row,diags1(QueensTail,DiagonalsTail).
diags2([],[]). diags2([(Row,Col)|QueensTail],[Diagonal|DiagonalsTail]):-Diagonal is Col+Row,diags2(QueensTail,DiagonalsTail).
eight_queens(Board):-length(Board,8),valid_board(Board),rows(Board,Rows),cols(Board,Cols),diags1(Board,Diags1),diags2(Board,Diags2),fd_all_different(Rows),fd_all_different(Cols),fd_all_different(Diags1),fd_all_different(Diags2).
测试结果如下:
|?-eight_queens([(1,A),(2,B),(3,C),(4,D),(5,E),(6,F),(7,G),(8,H)]).
A=1
B=5
C=8
D=6
E=3
F=7
G=2
H=4
总结
- 核心优势
- 自然语言处理
- 游戏
- 语义网
- 人工智能
- 调度:Prolog擅长处理有限资源。
- 不足
- 功用
- 超大数据集合
- 混合命令式和声明式模型