一、背景

在上一篇博客https://www.cnblogs.com/wkfvawl/p/12056951.html上,我简单介绍了一下Prolog的一些基本语法,这篇博客主要通过老师课上讲过的一个问题,来体验一下prolog在逻辑推理上的优势。

在介绍问题之前,先引入一道Prolog题:

试编写一个描述亲属关系的Prolog程序,然后再给出一些事实数据,建立一个小型演绎数据库。

提示:可以以父亲和母亲为基本基本关系(作为基本谓词),再由此描述祖父、祖母、兄弟、姐妹以及其他亲属关系。

    mother(a,c).
    mother(a,d).
    mother(c,g).
    mother(c,f).
    father(b,c).
    father(b,d).
    father(e,g).
    father(e,f).
    grandfather(X,Z):-father(X,Z),father(Y,Z).
    grandmother(X,Z):-mother(X,Y),mother(Y,Z).
    sister(X,Y):-mother(Z,X),mother(Z,Y).
    aunt(X,Y):-mother(Z,Y),sister(Z,X).

这里使用SWI-Prolog进行测试

注意,在 Prolog 中一个词若以小写开头,那么它是一个固定值,若以大写字母开头,则是一个变量。 

继续定义规则:

 family(F,M,C) :-father(F,C), mother(M,C).

提出查询,比如

prolog 将会求出X可能的取值并输出到控制台,中间的下划线是一个占位符,不会进行求值。有了上面的基础知识后,我们就可以利用它来解决逻辑问题了,下面就让我们来解决“斑马难题”吧。

二、爱因斯坦逻辑难题

有5间不同颜色的房间,每间住个不同国籍的人,每人有自己喜欢的饮料、香烟和宠物。已知信息:
英国人在红房间中
西班牙人有一条狗
挪威人住在左边第一间房里
黄房间中的人在抽库尔斯牌香烟
抽切斯菲尔德牌香烟的人是养了一只狐狸的人的邻居
挪威人住在蓝房间隔壁
抽温斯顿牌香烟的人有一只蜗牛
抽幸运牌香烟的人喝橘子汁
乌克兰人喝茶
日本人抽国会牌香烟
抽库尔斯牌烟的房间在有匹马的房间隔壁
绿房间中的人喝咖啡
绿房间在白房间的左边
中间房间的人喝牛奶

这道题的解题关键在于,要以一种清晰的方式将每个房子相关的属性(颜色、国籍、香烟、宠物、饮料、编号)列出来,前面5个提示中包含了5个国家,那可以利用这一点画出一个表格,每一行表示一个国家,每一列表示房子的一种属性。一步步根据提示得到一些推论,将结果填入表格,答案便渐渐清晰起来,使用这种人工方式推理的结果如下图所示:

虽然我们知道了解题的关键,但这个问题仍然需要经过很多步的推导才能得出结果,如果使用 prolog 那得到这个问题的答案就简单多了,只需要定义好事实和规则,然后向 Prolog 提出问题,逻辑引擎就会为我们查出结果来。

下面是解决这个问题的 Prolog 代码。

house(A,[A,_,_,_,_]).
house(A,[_,A,_,_,_]).
house(A,[_,_,A,_,_]).
house(A,[_,_,_,A,_]).
house(A,[_,_,_,_,A]).

left(A,B,[A,B,_,_,_]).
left(A,B,[_,A,B,_,_]).
left(A,B,[_,_,A,B,_]).
left(A,B,[_,_,_,A,B]).

middle(A,[_,_,A,_,_]).

first(A,[A,_,_,_,_]).

neighbor(A,B,[A,B,_,_,_]).
neighbor(A,B,[_,A,B,_,_]).
neighbor(A,B,[_,_,A,B,_]).
neighbor(A,B,[_,_,_,A,B]).
neighbor(A,B,[B,A,_,_,_]).
neighbor(A,B,[_,B,A,_,_]).
neighbor(A,B,[_,_,B,A,_]).
neighbor(A,B,[_,_,_,B,A]).

attr(Country,Pet,Color,Drink,Smoke).

all_houses(Houses) :-
    house(attr(britsh,_,red,_,_), Houses),
    house(attr(spain,dog,_,_,_), Houses),
    house(attr(japan,_,_,_,congressCigarettes), Houses),
    house(attr(ukraine,_,_,tea,_), Houses),
    house(attr(norway,_,_,_,_), Houses),
    first(attr(norway,_,_,_,_), Houses),
    left(attr(_,_,green,_,_), attr(_,_,white,_,_), Houses),
    house(attr(_,snail,_,_,winstonCigarettes), Houses),
    house(attr(_,_,yellow,_,coorsCigarettes), Houses),
    middle(attr(_,_,_,milk,_), Houses),
    house(attr(_,_,green,cafe,_), Houses),
    neighbor(attr(norway,_,_,_,_), attr(_,_,blue,_,_), Houses),
    house(attr(_,_,_,orange,luckCigarattes), Houses),
    neighbor(attr(_,fox,_,_,_), attr(_,_,_,_,chesfieldCigarettes), Houses),
    neighbor(attr(_,horse,_,_,_), attr(_,_,_,_,coorsCigarettes), Houses),

    house(attr(_,zebra,_,_,_), Houses),
    house(attr(_,_,_,water,_), Houses).

在事实部分,将房子看做一个整体,描述了房子在5所房子中、房子的左右关系、中间的房子处于什么位置、第一所房子处于什么位置、房子间的相邻关系以及每所房子拥有哪些属性。
规则部分包含了对题目中提示的描述和最终问题的描述,这些定义是为了告诉逻辑引擎,在求值时必须满足这些条件。
最终的查询为 all_houses(A) ,prolog 逻辑引擎将会查找出满足结果的房子数组,注意每所房子由它的属性组成,这样最后得到的结果为:

12-14 11:18