五一的时候我发现一本逻辑题目的小册子,买过来之后本想做做脑力体操消磨一下时间,可是做了不到3到题我就不耐烦了,闲下来我就想能否写一个程序让计算机去求解这类问题呢,经过整个假期的努力居然做成了,欣喜之余我忍不住就想把它共享出来。
首先我们看一下书里面的第一个题目:
斯科特先生、他的妹妹、他的儿子、他的女儿都是网球选手。关于这四人有以下的情况:
(1) 最佳选手的的孪生同胞与最差选手的性别相同。
(2) 最佳选手与最差选手的年龄相同
请问谁是最佳选手
对于这样的问题,我的第一个困难是如何让计算机理解它,经过分析我设计了一门描述这类问题的语言,只要输入这种语言的文件,计算机就能为你自动搜索出所要的结果。为了让读者对这门语言有一个感性的认识,下面给出描述最佳选手问题的输入:
所有人{} = {"斯科特","妹妹","儿子","女儿"};
性别{} = {"男","女"}; 年龄{} = {1,2,3,4};
年龄 year(所有人); 性别 sex(所有人);
所有人 同胞(所有人); 所有人 最差,最佳;
sex("斯科特") = "男"; sex("妹妹") = "女"; sex("儿子") = "男"; sex("女儿") = "女";
year("斯科特") > year("儿子"); year("斯科特") > year("女儿");
同胞("斯科特")="妹妹";同胞("妹妹")="斯科特";
同胞("儿子")="女儿";同胞("女儿") = "儿子";
最佳 != 最差;
year(最佳) = year(同胞(最佳)); //(1)最佳选手的的孪生同胞
sex(同胞(最佳)) != sex(最差); //(1)
year(最差) = year(最佳); //(2)
输出 { "最佳选手是: " 最佳 }
假如上面的文本保存在一个”最佳选手.txt”文件中,在得到了我的znlog.exe程序后在控制台下输入”znlog最佳选手.txt”就会产生 “最佳选手是: 女儿”的输出。
输入语言
要让计算机能够参与求解,首先需要解决的问题就是如何把问题输入进去。经过分析,我认为首先需要设计一门能够描述问题语言。有了描述语言之后求解的过程就是:用户将实际的问题生成一个用这门语言描述的文本文件,然后将文件告诉求解程序,求解程序分析语言,进行搜索,产生输出。
当然完全通用的描述语言远不是我一个人能够设计好的,就算prolog的解析分析程序也不是我能写成的,不过大多数(特别是我手头小册子中的)逻辑问题都不是太复杂,我发现它们可以用几个简单的语言就能描述。
集合定义
通常的逻辑问题一般是求解某些变量的值,而且逻辑问题中变量的可能取值是已经给定的有限多个,例如上面问题中最佳选手的可能取值就只能是:"斯科特","妹妹","儿子","女儿",这样我的语言中就首先需要有集合定义,下面是我对集合定义的语法设计:
集合名{} = {集合元素, 集合元素,……};
集合元素可以是数字或者字符串,但是同一个集合中集合元素不能同时包含数字和字符串。出于实现难度的考虑我限制数字只能是整数,数字集合可以使用[a,b]的语法,[a,b]表示从a开始到b的所有数,例如:
A{} = {[3,7],9,10}; 就等价于 A{} = {3,4,5,6,7,9,10};
在定义字符串集合的时候每个字符串都必须用””号括起来,例如下面是一个字符串集合的例子:
B{} = { “张三”,”李四”,”王五” };
每个集合定义必须占用一个单独的语句,每个语句使用;号作为结束符。
变量定义
说完集合之后就该说到变量了,每个变量在使用前都需要给出它的定义,变量定义语法为:
集合名 变量1, 变量2,……;
在定义变量时第一个需要给出的就是它的类型,也就是已经定义了的集合名字。这个类型规定了变量的取值范围。多个变量可以定义在一个语句中。
为了满足需要,我一共设计了三种变量,一种是单变量,一种是序变量,一种是参变量。
单变量是最简单的,例如语句 A a; 就定义一个单变量a,这个a的取值范围是A。
序变量稍微复杂一些,例如语句 B x[]; 就定义一个序变量x。一个序变量相当于n个单变量,n的个数等于类型中元素的个数,例如这里的x就相当于 B x1,x2,x3这三个变量,并且这三个变量的取值互不相同。序变量可以使用[]符号来使用它,例如可以使用x[1],x[2],x[3]语法来使用x,[]中的下标从1开始到类型的个数。序变量还可以使用()符号来使用它,例如 x(“张三”)表示”张三”在x中的序号,它相当于一个{1,2,3} x张三的变量。
参变量相当于一个函数,例如语句 A f(B); 就定义了一个参变量,参变量的定义语法为:
变量名(参数1类型,参数2类型,……)
由于时间有限我暂时只支持一个参数的定义方式。也就是说现在的znlog程序只接受语法为:变量名(参数类型); 的定义。
一个参变量相当于n个单变量,n的个数等于参数类型中元素的个数,例如上面的f相当于A f张三,f李四,f王五;这三个变量,参变量中各个单变量的取值可以相同。参变量可以使用f(“张三”)这样的语法来使用它。
条件定义
接下来该说到条件定义了,每个条件使用一个单独的语句;条件语句是一个可以很复杂的表达式。表达式中可以使用 + - * / %这几个代数运算符,可以使用> < >= <= = !=这几个比较运算符号,可以使用 & | ^ ! -> 这几个逻辑运算符号,优先级的排列是
!
* / %
+ -
> >= < <= = !=
& ^
|
->
另外条件中还可以使用 存在 和 任意 两个量词,它们的语法是
存在 x 属于 集合名, 其他表达式
任意x 属于 集合名, 其他表达式
例如下面是4阶幻方的条件定义:
取值{} = {1,2,3,4}; 编号{} = {[0,15]}; 取值 M(编号);
任意 x 属于 编号, 任意 y 属于 编号, x/4=y/4 | x%4=y%4 -> x=y | M(x)!=M(y);
输出定义
最后该说的就是输出定义了,znlog在进行搜索的时候,如果发现了一个满足所有条件的解,就会将结果输出出来,输出的方式由用户指定,输出定义的语法如下:
输出{ 输出元素, 输出元素, 输出元素,…… };
输出定义由关键字输出引导,然后在{}中给出所有的输出元素,每个输出元素可以是字符串,也可以是表达式,字符串中使用c风格的/n/t导出换行和制表符。