那么,Prolog,告诉我怎么走

今年四月左右,我心血来潮地为自己立了一个学习Prolog的目标——对,就是那门以逻辑编程和人工智能为卖点的语言。不仅要学会它的基本用法,还妄想用它像朋友圈广告里的Python那样,用来处理Excel文件中的大数据!

尽管处理大数据是开个玩笑,但学习Prolog的目标是真的。既然要学习一门编程语言,就必须找一本靠谱的教材。在无中生友之后,我选择了由谭浩强老先生主编的《Learn Prolog Now》作为入门读物。

尽管《Learn Prolog Now》的内容一点也不real world,却循序渐进、非常地适合初学者,每一章的结尾还准备了“上机题”。出人意料的是,仅仅在第三章就遇到了不会做的题目。在焦急地苦战一番未果后,我拖着疲惫的身躯搁置了它,继续学习后面的章节。

时隔五个月,我再次尝试解答这道题目。却惊喜地发现,只需要冷静地分析再仔细运用前三章学过的知识,解决这道题目也就是水到渠成的事情了。

所以到底是个什么题?

讲了这么多,是时候揭晓这它的真面目了。由于第三题以第二题为基础,因此一并搬运了过来

感兴趣的朋友也可以直接移步源网页查看。

看完上面的题目,只学过主流编程语言的朋友大概会是一头雾水,毕竟无论是代码还是术语,都与平日里使用的大相径庭。我来试着解释一下。像byCar(auckland, hamilton)byTrain(metz, frankfurt)这样的代码,用Prolog的术语来讲叫做“事实”。就像数学中的公理一样,它们总是成立的。如果向Prolog提问,它会给出肯定的回答

byCarbyTrain被称为“谓词”,aucklandhamilton则是“原子”。

第二题要求定义travel/2,第三题要求定义travel/3travel是谓词的名字,2和3则是它所接受的参数的个数。定义一个谓词就是给出描述它何时成立的“规则”,举个例子,可以定义一个名为len的谓词,只有当第二个参数等于第一个参数的长度时才成立

以大写字母开头的标识符(如题目中的X,上图中的TL)是变量,在归一化(unification)时Prolog能够为它们赋值使得查询成立。

鉴于本文不是Prolog的入门教程,各位读者如果想进一步了解Prolog,还请移步《Learn Prolog Now》的相关章节。

先解决第二题吧

讲了这么多,该进入正题了。第二题其实不难,细心的读者应该已经发现,这题可以用递归来解决(就像上文的len一样)。

设谓词travel的两个参数分别叫做SE,各代表起点和终点。显然,travel(S, E)成立,当且仅当:

  1. 可以从S搭乘汽车(byCar)、火车(byTrain),或飞机(byPlane)抵达E,或者;
  2. 存在另一个城市M,可以从S搭乘汽车、火车,或飞机抵达M,并且travel(M, E)也成立。

上述算法可以轻松地写成Prolog代码

byCar(auckland,hamilton).
byCar(hamilton,raglan).
byCar(valmont,saarbruecken).
byCar(valmont,metz).

byTrain(metz,frankfurt).
byTrain(saarbruecken,frankfurt).
byTrain(metz,paris).
byTrain(saarbruecken,paris).

byPlane(frankfurt,bangkok).
byPlane(frankfurt,singapore).
byPlane(paris,losAngeles).
byPlane(bangkok,auckland).
byPlane(singapore,auckland).
byPlane(losAngeles,auckland).

travel(S, E) :- just_go(S, E).
travel(S, E) :- just_go(S, M), travel(M, E).

just_go(S, E) :- byCar(S, E).
just_go(S, E) :- byTrain(S, E).
just_go(S, E) :- byPlane(S, E).

让Prolog告诉咱们这个travel/2写得对不对

精彩!

你话我猜?

Prolog不仅知道一个查询是否成立,还知道这个查询在什么参数下成立。例如,可以让Prolog告诉咱们,从valmont可以抵达哪一些城市,以及哪一些城市可以抵达auckland

这正是在接下来的题目中需要发扬光大的能力。

终于来到第三题

第三题所要求的travel是一个接受三个参数的谓词,第三个参数由从起点到终点的途径城市构成。设这个新的变量为R,那么travel(S, E, R)成立当且仅当:

  1. 可以从S抵达E,并且Rgo(S, E),或者;
  2. 存在另一个城市M,以及另一条路径R2。可以从S抵达M,并且travel(M, E, R2)成立,并且Rgo(S, M, R2)

那么如何在规则中描述R的结构呢?莫非是像上面的谓词len那样,在:-的右侧写上形如R is go(S, M, R2)这样的代码?

并不是。

借助Prolog强大的模式匹配能力,只需要在:-的左边声明R的结构即可

byCar(auckland,hamilton).
byCar(hamilton,raglan).
byCar(valmont,saarbruecken).
byCar(valmont,metz).

byTrain(metz,frankfurt).
byTrain(saarbruecken,frankfurt).
byTrain(metz,paris).
byTrain(saarbruecken,paris).

byPlane(frankfurt,bangkok).
byPlane(frankfurt,singapore).
byPlane(paris,losAngeles).
byPlane(bangkok,auckland).
byPlane(singapore,auckland).
byPlane(losAngeles,auckland).

travel(S, E, go(S, E)) :- just_go(S, E).
travel(S, E, go(S, M, R)) :- just_go(S, M), travel(M, E, R).

just_go(S, E) :- byCar(S, E).
just_go(S, E) :- byTrain(S, E).
just_go(S, E) :- byPlane(S, E).

加载这段代码后,就能让Prolog告诉我们,如何从valmont去往losAngeles

Prolog不仅找出了题目中所给出的答案(见上图的第二行X =),还找出了另外一条可行的路径。

后记

确实不难,难怪可以作为第三章的习题。

阅读原文

你可能感兴趣的