Prolog小程序

来源:互联网 发布:php实例化一个类 编辑:程序博客网 时间:2024/04/29 15:43

坚决鄙视一介绍Prolog就用家谱树做例子的帖子/教材/指南。跟我八辈子不搭界的某银的家谱关我P事啊?就算自己的家谱。我爷爷的姑姑的侄儿的表姐的大舅子关我P事啊?

和朋友聊到用Prolog实现上次借助amb解决的谜题。Prolog内置了搜索和裁剪。写出的代码近乎琐碎,根本不需要amb。想想看,用amb时,那些描述约束的语句(调用must()函数的那几行)在选择语句之后执行,但竟然能左右之前被执行的语句的执行结果。而所有的细节都被封装在一个平淡的callcc里面。程序员可以专注于描述问题,而不用考虑代码执行的细节(在某种程度上),甚至不用考虑代码执行的顺序,这是何等强大的抽象和表达能力。而Prolog把这种抽象能力在语言级别实现,更进一步。只不过上次用和伪代码无异的Ruby代码都让很多老大望而却步,用句法和语义与流行编程语言迥异的Prolog不是更费力不讨好?再说代码本身也不出奇,不能给已经知道Prolog的老大们带来快感,用作简介恐怕又不够周详。不过既然写出来了,就忍不住手痒想贴出来。典型的egoblogging。先鄙视一下自己。
 
Prolog也是门“古老”的语言。Prolog是PROgramming in LOgic的缩写。它不是最早的逻辑编程语言(最早的逻辑编程语言1964年左右就做出来了),不过大概是最有名的。1971年, 29岁的法国人Alain Colmerauer在加拿大出名的花花城市蒙特利尔亲切会见了刚拿到教职的比利时人Robert Pasero和Phlippe Roussel。Alain是个法国愤青,热衷于搞一个当时看来颇为激进的研究项目:探索用自然语言进行逻辑推理。而选择的自然语言便是是——还用猜么——自然是法语。法国银就好这一口。为了推进项目,他们决定实现一套系统,帮助他们理解自动定理证明和自然语言处理,于是花了大量精力研究怎么编写自动定理证明机和自然语言处理程序。结果无心插柳,1972年,Prolog的雏形问世。同年,一个叫Dennis Ritche的大胡子推出了一门叫C的语言。一个叫Alan Kay的小胡子也推出一门叫Smalltalk-72的语言。三年后,MIT两个没留胡子的巫师推出了一门叫Scheme的语言。后面的东西已是历史。不鸟C语言的计算机科学家们郁闷地看着C席卷世界每一个角落。不鸟其他一切语言的Smalltalker们郁闷地看着Java借Smalltalk的东风也席卷世界每一个角落。声明式编程(declarative programming)的铁杆拥趸们把玩着他们心爱的Prolog,坚信有一天人们会幡然领悟:声明式编程才是王道。而LISPer们依然喜欢对每一项热点技术做出同样的评论:这个东东Lisp里早已有了。

已经很久没写Prolog。幸好这坨程序比Ruby版的程序还直观。只是绝大部分库函数都忘了,就实现了几个阳春版的。从下面的代码可见,代码比用amb()的还简单。直接把题目的条件描述一下,系统自动找出合适的答案。想自己动手的老大们,可以先跳到这里安装SWI-Prolog。安装好了后,在安装目录下有个bin目录。目录下的plcon.exe进入控制台程序。而plwin.exe启动一个简单的IDE: 

方便起见,再贴一下题目:Baker, Cooper, Fletcher, Miller, Smith住在一动5层公寓的不同楼层。Baker不在顶楼住。Cooper不在底楼住。Fletcher既不在顶楼也不在底楼。Miller住的楼层比Cooper住的高。SmithFletcher的楼层不相邻。FletcherCooper的楼层不相邻。问每个人住的楼层。

最后的代码如下: 

 
[X|Xs]表示一个表。X是第一个元素,而Xs是子表。比如说,如果[X|Xs]=[1, 2, 3, 4, 5],那X就等于1, 而Xs等于[2, 3, 4, 5]。下划线,_, 是一个特殊变量,表示“无所谓”。比如说[_|Xs]表示一个表的第一个元素可以是任何值。[X|_]表示一个表的子表可以是任何值。操作符=/=相当于流行编程语言里的!=。逗号表示逻辑的and, 相当与类C语言里的&&。句号表示一行语句结束。任意两行的关系是逻辑的或,相当于类C语言里的 ||。比如说开头两行表示一个member这个函数要为真的话,要么满足第一行的条件,要么满足第二行的条件。
 
Prolog的基本思路是模式匹配,基本数据结构是表。每一个函数其实是一个谓词判断。以头两行实现的函数member为例:member接受两个参数。第一个参数一个变量,第二个参数是一个表。当第一个参数是表里的元素时,member返回true(prolog里用yes),否则返回false(prolog里用no)。第一种情况member(X, [X|_])的意思是,如果一个元素X和表里的第一个元素一样,调用该函数就一定得到真值。注意,我们在这里并不关心X的具体值,只要X和表里第一个元素匹配,我们就知道X是表的元素了。正因为如此,我们也不关心子表的值,所以用一个特殊的变量,_, 来表示子表可以是任何东东。下一种情况也就了然:如果X和表里第一个元素不一样,那么我们递归地考察X是不是在子表里:member(X, Ys)。
 
关于模式匹配。实际执行一下就可以看出来了。比如说我们执行member(X, [1, 2, 3]):
  1. 先加载写好的amb.pl文件。因为每一个调用都是执行谓词断言,所以我们可以看到成功执行后,一定会得到一个安慰人心的yes。


  2. 执行member(X, [1, 2, 3]): 显然X等于1时,第一行语句,member(X, [X|_])被匹配。所以X可以为1。


  3. 如果不满足,输入分号,表示继续搜索(如果我们只想找一个答案,也可以直接敲入回车):



  4. 继续输入分号,


  5. 如果再输入分号,Prolog找无可找,就会返回no, 表示没有更多的匹配了:


  6. 如果是单纯的判断,Prolog直接输出结果:





  7. 如果我们打开调试开关,就更能看出匹配的原理了。系统每次自动搜索匹配的值,并内建变量跟踪搜索的结果。


 
 
最后一个函数根本就是对题目一对一的翻译。不能再简单了吧?都不知道有什么可以再解释的。

 

原创粉丝点击