Prolog 是一种与众不同的语言,不用来开发软件,专门解决逻辑问题。比如,"苏格拉底是人,人都会死,所以苏格拉底会死"这一类的问题。
Prolog 就是"逻辑编程"(programming of Logic)的意思。只要给出事实和规则,它会自动分析其中的逻辑关系,然后允许用户通过查询,完成复杂的逻辑运算。
本文简单介绍如何使用 Prolog 语言,主要参考了 xmonader 的教程。
一、SWI-Prolog
学习之前,请安装 Prolog 的运行环境 SWI-Prolog,才能运行后面的代码。
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).
上面代码中,X
和Y
都是大写,表示这是两个变量。符号:-
表示推理关系,含义是只要右边的表达式friend(Y, X)
为true
,那么左边的表达式friend(X, Y)
也为true
。因此,根据这条规则,friend(jack, peter)
就可以推理得到friend(peter, jack)
。
如果一条规则取决于多个条件同时为true
,则条件之间使用逗号分隔。
mother(X, Y) :- child(Y,X), female(X).
上面代码中,X
是Y
的母亲(mother(X, Y)
)取决于两个条件:Y
是X
的小孩,X
必须是女性。只有这两个条件都为true
,mother(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 如何解决实际问题。
我们知道,地图的相邻区域不能使用同一种颜色。现在有三种颜色:红、绿、蓝。请问如何为上面这幅地图着色?
首先,定义三种颜色。
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),
注意,上面代码中Bathroom
和Bag
这样的字符串,都是大写字母开头,所以都是变量,代表对应的人。至于具体是谁,就要通过推理得到。
线索一:厨房里面是一个男人,那里的凶器不是绳索、刀子、包和火枪。
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 ;
(完)
timgao 说:
最害怕的莫过于复杂的逻辑题,我是个假的程序猿[捂脸]!
2019年1月28日 09:33 | # | 引用
孙老大 说:
最近在看AI的书,原来有现成的语言来方便的实现“逻辑推导”,真方便!时不时来看看阮老师的文章总是有惊喜,一是节省时间 二是扩展视野,感谢????阮老师!!!
2019年1月28日 09:55 | # | 引用
ben 说:
头一次听说还有这样的语言!太有意思了!
2019年1月28日 10:38 | # | 引用
nlog 说:
感觉很有意思的语言
2019年1月28日 10:55 | # | 引用
ixx 说:
这个语言再加一个可视化的逻辑输入界面就更完美了,编程毕竟还是复杂有些题把编程思路理清楚可能就有答案了,也不用他帮你算了
2019年1月28日 11:00 | # | 引用
lettyolo 说:
线索八 是不是 George 所在的那间屋子里面有火枪哇?
2019年1月28日 11:01 | # | 引用
阮一峰 说:
@lettyolo:
谢谢指出,已经改过来了。确实 George 的屋子里有火枪。
2019年1月28日 11:20 | # | 引用
小鱼 说:
friend(john, Who).
只出来一个结果
Who = julia .
2019年1月28日 11:36 | # | 引用
阮一峰 说:
@小鱼:
需要手动输入一个分号,才会显示后面的结果。
2019年1月28日 11:40 | # | 引用
某可儿 说:
总觉得对于高复杂的逻辑运算,用计算机来处理确实可以避免很多的人的能力限制,而且可以推广开来,降低很多事件的处理成本。不过还是存在问题就是在高度复杂的前提下,一旦总结的pl代码中出现微小的差错,那么带来的问题可能会很难处理。不知道这方面以后会不会有配套的纠错的方法
2019年1月28日 12:21 | # | 引用
Kevin 说:
以前就想,能不能有这种逻辑判断的程序,还真被人发明出来了。不知道可不可以调试,如果逻辑出错的话。
2019年1月28日 15:04 | # | 引用
Frank 说:
Prolog习题: https://sites.google.com/site/prologsite/prolog-problems/
2019年1月29日 05:47 | # | 引用
BearBig 说:
真实情况可能会更复杂吧,比如说犯人会混淆视听或者制造不在场证据,不过还是大开眼界,居然还有推断关系的语言
2019年1月29日 07:10 | # | 引用
Abner 说:
一直知道Prolog, 感觉没啥用处, 今天才发现如此强大, 特别适合抽象的问题.
如果要用程序语言写的话, 需要想人名用什么数据结构, 如何遍历等等具体的实现, 而脱离了逻辑思考的本身. 感谢好文.
2019年1月29日 07:23 | # | 引用
滑稽 说:
这个是真的厉害
PY很难做到
2019年1月29日 12:33 | # | 引用
戈饭 说:
第一次看到这么神奇的语言,和传统的编程方法有本质的区别
2019年1月29日 13:38 | # | 引用
魏文豪 说:
在sicp里看到 逻辑编程语言 没法判断 !!true 这样的问题.
2019年1月29日 14:32 | # | 引用
Alan 说:
要是用在狼人杀...
2019年1月29日 16:31 | # | 引用
brukfeng 说:
学习了,很有趣
2019年1月29日 17:49 | # | 引用
周志鹏 说:
某些场景还是挺实用的!赞!
2019年1月30日 11:41 | # | 引用
Sag 说:
有点意思
2019年1月30日 16:18 | # | 引用
Tom 说:
请问第三个例子执行完colorify(A,B,C,D,E). 后按";"几次到结果输出完毕后,最后返回了false.是为什么?
2019年2月 1日 10:37 | # | 引用
月丶基拉 说:
我跑`crime.pl`这个逻辑代码时,大致错误提示规则的定义不能要多余的变量,改成这样才能跑成功,这是语言版本问题吗,还是需要在哪去设置一下?
```
clue1(Kitchen, Bag, Knife, Firearm, Rope),
clue2(Bathroom, Study),
clue3(Bathroom, Dining, Bag),
clue4(Study, Rope),
clue5(Livingroom),
clue6(Dining, Knife),
clue7(Pantry, Study),
clue8(Firearm)
```
2019年2月 1日 15:08 | # | 引用
山石松 说:
一时间想不到能在工作中哪里用到这个。。。。
2019年2月12日 14:31 | # | 引用
vincent.luan 说:
这个语言历史也很悠久了。最近人工智能比较火。看来又要用起来了。
2019年2月18日 11:48 | # | 引用
CASSIE_LIU 说:
当所有结果被找出之后 会返回false
2019年2月22日 04:56 | # | 引用
Harris ZHANG 说:
阮老师,我有一个问题:
在2.2中:
如果 friend(jack,peter) 表示 jack 的朋友是 peter
则2.3中:
mother(X,Y) 就表示 ,X的mother 是Y
且 child(Y,X) 表示 , Y的 child 是X
这表示Y是母亲 X是小孩 ,与2.3 原文中X是母亲相矛盾。
所以若 X是Y的母亲; Y是X的小孩 则可推出 Jack 是peter的 friend 是括号里第一个变量拥有属性。
2019年3月14日 08:29 | # | 引用
archxm 说:
SQL不就是这样的语言么?
2019年3月15日 17:35 | # | 引用
hucsmn 说:
prolog有个变体datalog也很有意思。一些问题用datalog来做,比prolog更合适
2019年4月 1日 03:55 | # | 引用
冷月清风 说:
非常好的帖子,版主辛苦
2019年4月16日 10:22 | # | 引用
Lellansin 说:
感觉很像离散数学里面的一些推导情况。感谢。
2019年5月 5日 17:24 | # | 引用
bigheadghost 说:
其实解决逻辑推理问题也不用特意去学一门新语言,去自己工具箱里找个趁手的兵器也行。
写了一篇文章,采用Mathematica解题,可与本文对照阅读,文末还求解了一道更复杂的题目。
欢迎移步:https://bigheadghost.github.io/zh/post/bruteforce_reasoning/
2019年5月25日 13:08 | # | 引用
wyf 说:
跑了一下谁是凶手,凶手应该有三个解,christine、robert和john。
murderer is :christine
murderer is :christine
murderer is :christine
murderer is :christine
murderer is :christine
murderer is :robert
murderer is :john
2019年6月10日 17:46 | # | 引用
wyf 说:
然后发现少写条件了(晕)
2019年6月10日 17:49 | # | 引用
vivi 说:
阮老师,我最近刚学习逻辑推理,下面这个问题想用prolog编程推理一下,一直没有思路。能否指导一下,给个下面这个问题的例程:
在一个4X4的格子世界中,一个寻宝人需要找到金子。在这个世界里,有陷阱(T )、怪兽(M)和金子(G)
这个世界模型:
1)只有一个怪兽;
2)若一个格子里有怪兽,则寻宝人会在这个格子周边(不包括对角线)闻到臭味。
3)若一个格子里有陷阱,则寻宝人会在这个格子周边格子里感受到风的吹拂。
谢谢。
2020年4月 2日 10:14 | # | 引用
qwertyegg 说:
很好的文章,提一个意见
线索5的陈述觉得应该是
\+Livingroom=robert, \+Livingroom=barbara, \+Livingroom=christine, \+Livingroom=yolanda,
因为没有说起居室里是一个男人
线索9里面的三个等式其实只需要两个即可
2020年5月19日 09:21 | # | 引用
zjm 说:
运行第二个程序,只得到 X=chritine,没有其他信息,如何获得?
2020年6月 8日 20:08 | # | 引用
专家系统开发者 说:
请问运行第二个程序大概需要多长时间?
2020年8月17日 05:48 | # | 引用
专家系统开发者 说:
我用专家系统工具做第二题,运行时间不到2秒。
2020年8月18日 03:45 | # | 引用
。 说:
```
friend(jack,jack).
```
会卡死,是因为循环引用?
2020年10月12日 04:44 | # | 引用
null 说:
逗号and
分号or
and和or应该是逻辑编程的基础
2020年11月19日 22:31 | # | 引用
ADI 说:
\+ A=B 代表 A 不等于 B,还是看原版更细节。
\+ X=Y means X is not equal to Y
2021年3月31日 10:14 | # | 引用
张斌 说:
博主你好,在Prolog 语言入门教程中,hello.pl脚本放在哪个目录下?[windows 10操作系统]谢谢
2021年8月10日 22:28 | # | 引用
周扒皮 说:
这用来去考公务员考试太棒了
2021年9月16日 15:10 | # | 引用
Lemonix 说:
第二个例子会报错....ERROR: Unknown procedure: friend/2 (DWIM could not correct goal)
2022年2月 6日 10:29 | # | 引用
june 说:
你好。线索二感觉有点欠严谨,线索二只说Barbara和Yolands在,没说christine不在呀。
2022年3月11日 13:45 | # | 引用
xdgg 说:
读起来有点怪怪的, 反直觉的变量命名
2023年2月27日 12:08 | # | 引用
xdgg 说:
比如, 程序分析可以用 Datalog, 等等
2023年2月27日 12:31 | # | 引用
xdgg 说:
是结合了 uniq_ppl 规则, 消去了一些明显不符合的谓词. 其实结合 uniq_ppl 可以将 clue2 写得更简洁些, `sub_clue2(Bathroom, Study) :- barbara = Bathroom, yolands = Study; barbara = Study, yolands = Bathroom.`
2023年2月27日 12:36 | # | 引用