35

Prolog 语言入门教程

 5 years ago
source link: http://www.ruanyifeng.com/blog/2019/01/prolog.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Prolog 是一种与众不同的语言,不用来开发软件,专门解决逻辑问题。比如,"苏格拉底是人,人都会死,所以苏格拉底会死"这一类的问题。

7zm2yme.jpg!web

Prolog 就是"逻辑编程"(programming of Logic)的意思。只要给出事实和规则,它会自动分析其中的逻辑关系,然后允许用户通过查询,完成复杂的逻辑运算。

本文简单介绍如何使用 Prolog 语言,主要参考了 xmonader 的 教程

一、SWI-Prolog

学习之前,请安装 Prolog 的运行环境 SWI-Prolog ,才能运行后面的代码。

22MJJnJ.jpg!web

SWI-Prolog 官网有各个操作系统的 二进制安装包 ,下载即可。Debian / Ubuntu 系统还可以用下面的命令。

$ sudo apt-get install swi-prolog

安装以后,Linux 系统可以命令行启动。

$ swipl
?-

然后,就进入了 Prolog 运行环境, ?- 是命令提示符。下面是 Hello world 的例子。

?- write("Hello, world").
Hello, world!
true.

上面命令输出 Hello world。

有几个地方需要注意。Prolog 所有语句的结尾都用一个"点"( . )表示结束。 write() 是打印命令。命令本身就是一个表达式,输出完成以后,返回值就是 true. ,也会显示出来。

如果想在 Hello world 之间插入一个换行,可以使用 nl 命令。

?- write('Hello,'), nl, write('world').
Hello,
world
true.

退出 SWI-Prolog,可以使用 halt 命令,别忘了后面还要加一个点。

?- halt.

二、基本语法

2.1 常量和变量

Prolog 的变量和常量规则很简单:小写字母开头的字符串,就是常量;大写字母开头的字符串,就是变量。

?- write(abc).
abc
true.

?- write(Abc).
_3386
true.

上面代码中, abc 是常量,输出就是自身; Abc 是变量,输出就是该变量的值。

2.2 关系和属性

两个对象之间的关系,使用括号表示。比如,jack 的朋友是 peter,写成 friend(jack, peter).

注意,jack 的朋友是 peter,不等于 peter 的朋友是 jack。如果两个人都认为对方是朋友,要写成下面这样。

friend(jack, peter).
friend(peter, jack).

如果括号里面只有一个参数,就表示对象拥有该属性,比如 jack 是男性,写成 male(jack).

2.3 规则

规则是推理方法,即如何从一个论断得到另一个论断。

举例来说,我们定下一条规则:所有朋友关系都是相互的,规则写成下面这样。

friend(X, Y) :- friend(Y,X).

上面代码中, XY 都是大写,表示这是两个变量。符号 :- 表示推理关系,含义是只要右边的表达式 friend(Y, X)true ,那么左边的表达式 friend(X, Y) 也为 true 。因此,根据这条规则, friend(jack, peter) 就可以推理得到 friend(peter, jack)

如果一条规则取决于多个条件同时为 true ,则条件之间使用逗号分隔。

mother(X, Y) :- child(Y,X), female(X).

上面代码中, XY 的母亲( mother(X, Y) )取决于两个条件: YX 的小孩, X 必须是女性。只有这两个条件都为 truemother(X, Y) 才为 true

如果一条规则取决于某个条件为 false ,则在条件之前加上 \+ 表示否定。

onesidelove(X, Y) :- loves(X, Y), \+ loves(Y,X).

上面代码中, X 单相思 Y ,取决于两个条件。第一个条件是 X 喜欢 Y ,第二个条件是 Y 不喜欢 X

2.5 查询

Prolog 支持查询已经设定的条件。我们先写一个脚本 hello.pl

friend(john, julia).
friend(john, jack).
friend(julia, sam).
friend(julia, molly).

然后在 SWI-Prolog 里面加载这个脚本。

?- [hello].
true.

上面代码中, true. 是返回的结果,表示加载成功。

然后,可以查询两个人是否为朋友。

?- friend(john, jack).
true.

?- friend(john, sam).
false.

listing() 函数可以列出所有的朋友关系。

?- listing(friend).
friend(john, julia).
friend(john, jack).
friend(julia, sam).
friend(julia, molly).

true.

还可以查询 john 有多少个朋友。

?- friend(john, Who).
Who = julia ;
Who = jack.

上面代码中, Who 是变量名。任意的变量名都可以,只要首字母为大写。

三、地图着色问题

下面看看 Prolog 如何解决实际问题。

zemY3qF.png!web

我们知道,地图的相邻区域不能使用同一种颜色。现在有三种颜色:红、绿、蓝。请问如何为上面这幅地图着色?

首先,定义三种颜色。

color(red).
color(green).
color(blue).

然后,定义着色规则。

colorify(A,B,C,D,E) :-
    color(A), color(B), color(C), color(D), color(E),
    \+ A=B, \+ A=C, \+ A=D, \+ A=E,
    \+ B=C, \+ C=D, \+ D=E.

上面代码中, colorify(A,B,C,D,E) 是一个对 ABCDE 五个变量求值的表达式。该表达式为 true 的条件是,这五个变量各自为一种颜色,则相邻的变量不相等。

最后,这两段代码合在一起,组成一个脚本 map.pl ,再加载这个脚本。

?- [map].
true.

执行表达式 colorify(A,B,C,D,E) ,SWI-Prolog 就会将三种颜色依次赋值给变量,测试哪些组合是可能的结果。

?- colorify(A,B,C,D,E).
A = red,
B = D, D = green,
C = E, E = blue;
A = red,
B = D, D = blue,
C = E, E = green ;
A = green,
B = D, D = red,
C = E, E = blue ;
A = green,
B = D, D = blue,
C = E, E = red ;
A = blue,
B = D, D = red,
C = E, E = green ;
A = blue,
B = D, D = green,
C = E, E = red ;

可以看到,计算机给出了6组解,即有6种可行的地图着色方法。

四、谁是凶手

下面看一个比较有趣的逻辑题。

Boddy 先生死于谋杀,现有六个嫌疑犯,每个人在不同的房间,每间房间各有一件可能的凶器,但不知道嫌疑犯、房间、凶器的对应关系。请根据下面的条件和线索,找出谁是凶手。

已知条件:六个嫌疑犯是三男(George、John、Robert)三女(Barbara、Christine、Yolanda)。

man(george). man(john). man(robert).
woman(barbara). woman(christine). woman(yolanda).

为了后面解题的方便,需要把"男人"和"女人"都定义为"人"。

person(X):- man(X).
person(X):- woman(X).

六个嫌疑犯分别待在六个房间:浴室(Bathroom)、饭厅(Dining Room)、厨房(Kitchen)、起居室(Living Room)、 储藏室(Pantry)、书房(Study)。每间房间都有一件可疑的物品,可以当作凶器:包(Bag)、火枪(Firearm)、煤气(Gas)、刀(Knife)、毒药(Poison)、绳索(Rope)。

location(bathroom). location(dining). location(kitchen).
location(livingroom). location(pantry). location(study).
weapon(bag). weapon(firearm). weapon(gas). 
weapon(knife). weapon(poison). weapon(rope).

下面声明一条规则,每个房间的人都是不一样的。

uniq_ppl(A,B,C,D,E,F):- 
  person(A), person(B), person(C), 
  person(D), person(E), person(F),  
  \+A=B, \+A=C, \+A=D, \+A=E, \+A=F, 
  \+B=C, \+B=D, \+B=E, \+B=F, 
  \+C=D, \+C=E, \+C=F, 
  \+D=E, \+D=F, 
  \+E=F.

然后,定义一个表达式 murderer(X) ,变量 X 就是凶手。该表达式只有满足以下所有条件,才可能为 true

murderer(X) :-
   uniq_ppl(Bathroom, Dining, Kitchen, Livingroom, Pantry, Study),
   uniq_ppl(Bag, Firearm, Gas, Knife, Poison, Rope),

注意,上面代码中 BathroomBag 这样的字符串,都是大写字母开头,所以都是变量,代表对应的人。至于具体是谁,就要通过推理得到。

线索一:厨房里面是一个男人,那里的凶器不是绳索、刀子、包和火枪。

man(Kitchen), 
   \+Kitchen=Rope, \+Kitchen=Knife, \+Kitchen=Bag, \+Kitchen=Firearm,

线索二:Barbara 和 Yolanda 在浴室和书房。

woman(Bathroom), woman(Study), 
  \+christine=Bathroom, \+christine=Study, 
  \+barbara=Dining, \+barbara=Kitchen, 
  \+barbara=Livingroom, \+barbara=Pantry,
  \+yolanda=Dining, \+yolanda=Kitchen, 
  \+yolanda=Livingroom, \+yolanda=Pantry,

线索三:带包的那个人不是 Barbara 和 George,也不在浴室和饭厅。

\+barbara=Bag, \+george=Bag, 
\+Bag=Bathroom, \+Bag=Dining,

线索四:书房里面是一个带绳子的女人。

woman(Rope), Rope=Study,

线索五:起居室里面那件凶器,与 John 或 George 在一起。

man(Livingroom), \+Livingroom=robert,

线索六:刀子不在饭厅。

\+Knife=Dining,

线索七:书房和食品储藏室里面的凶器,没跟 Yolanda 在一起。

\+yolanda=Pantry, \+yolanda=Study,

线索八:George 所在的那间屋子里面,没有火枪。

Firearm=george,

线索九:Boddy 先生死在食品储藏室里,那里的凶器是煤气。

Pantry=Gas, Pantry=X, Gas=X,

线索就是上面这些,然后把写好的所有表达式放在一起,组成一个完整的脚本 crime.pl ,代码看 这里

加载这个脚本,执行 murderer(X) 函数,由于条件复杂,运算时间较长,最终会显示凶手是谁。

?- [crime].
true.

?- murderer(X).
KILLER IS :christine
Bathroom: yolanda
Dining: george
Livingroom: john
Pantry: christine
Study: barbara
Kitchen: robert
Knife: yolanda
Gas: christine
Rope: barbara
Bag: john
Poison: robert
Firearm: george
X = christine ;

(完)


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK