七周七语言——Prolog(三)

今天来看几个详细的例子,看看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).

继续给对角线编号。给左上角到右下角的对角线赋一个值Col-Row。下面是抓取对角线元素的谓词:
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

总结

  • 核心优势
  1. 自然语言处理
  2. 游戏
  3. 语义网
  4. 人工智能
  5. 调度:Prolog擅长处理有限资源
  • 不足
  1. 功用
  2. 超大数据集合
  3. 混合命令式和声明式模型





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值