在prolog中定义数学语言

时间:2017-05-14 11:50:04

标签: prolog

所以我有这种数学语言,它是这样的:

E -> number
[+,E,E,E]  //e.g.  [+,1,2,3]  is  1+2+3 %we can put 2 to infinite Es here.
[-,E,E,E]  //e.g.  [-,1,2,3]  is  1-2-3 %we can put 2 to infinite Es here.
[*,E,E,E]  //e.g.  [*,1,2,3]  is  1*2*3 %we can put 2 to infinite Es here.
[^,E,E]    //e.g.  [^,2,3]    is  2^3
[sin,E]    //e.g.  [sin,0]    is  sin 0
[cos,E]    //e.g.  [cos,0]    is  cos 0

我想编写一组规则,在prolog中找到由这种语言编写的数学表达式的数值。

我首先编写了一个名为" check"的函数,它根据我们的语言检查列表是否以正确的方式编写:

check1([]).
check1([L|Ls]):- number(L),check1(Ls).
check([L|Ls]):-atom(L),check1(Ls).

现在我需要编写功能"评估"它采用一个列表,该列表是由该语言编写的表达式,以及一个变量,它是与该语言对应的数值。 例如:

?-evaluate([*,1,[^,2,2],[*,2,[+,[sin,0],5]]]],N) -> N = 40

所以我写了这个:

sum([],0).
sum([L|Ls],N):- not(is_list(L)),sum(Ls,No),N is No + L.
min([],0).
min([L|Ls],N):-not(is_list(L)), min(Ls,No),N is No - L.
pro([],0).
pro([X],[X]).
pro([L|Ls],N):-not(is_list(L)), pro(Ls,No), N is No * L.
pow([L|Ls],N):-not(is_list(L)), N is L ^ Ls.
sin_(L,N):-not(is_list(L)), N is sin(L).
cos_(L,N):-not(is_list(L)), N is cos(L).

d([],0).
d([L|Ls],N):- L == '+' ,sum(Ls,N);
L == '-',min(Ls,N);
L == '*',pro(Ls,N);
L == '^',pow(Ls,N);
L == 'sin',sin_(Ls,N);
L == 'cos',cos_(Ls,N).

evaluate([],0).
evaluate([L|Ls],N):-
is_list(L) , check(L) , d(L,N),L is N,evaluate(Ls,N);
is_list(L), not(check(L)) , evaluate(Ls,N);
not(is_list(L)),not(is_list(Ls)),check([L|Ls]),d([L|Ls],N),
L is N,evaluate(Ls,N);
is_list(Ls),evaluate(Ls,N).

并且它只是为了一个列表并返回正确的答案,但不是主列表中的多个列表,我的代码应该如何?

1 个答案:

答案 0 :(得分:3)

您使用的规范看起来像是一个生产规则,它描述了E(可能是 Expression 的缩写)可能是6个指定操作中的一个或一个。那是空列表[]不是表达式。所以事实

evaluate([],0).

不应该在您的代码中。您的谓词sum / 2几乎与您编写它的方式一致,除了空列表和具有单个元素的列表,根据您的规范,它们不是有效输入。但是谓词min / 2和pro / 2不正确。请考虑以下示例:

   ?- sum([1,2,3],X).
X = 6                     % <- correct
   ?- sum([1],X).
X = 1                     % <- incorrect
   ?- sum([],X).
X = 0                     % <- incorrect
   ?- min([1,2,3],X).
X = -6                    % <- incorrect
   ?- pro([1,2,3],X).
X = 6 ? ;                 % <- correct
X = 0                     % <- incorrect

从数学上讲,加法和乘法是associative但是减法不是。在编程语言中,所有这三个操作通常都是左关联的(参见例如Operator associativity)以产生数学上正确的结果。也就是说,将计算上述查询中的减法序列:

1-2-3 = (1-2)-3 = -4

定义这些操作序列的方式类似于以下计算:

[A,B,C]: ((0 op C) op B) op A

这对于补充来说很好:

[1,2,3]: ((0 + 3) + 2) + 1 = 6

但它不适用于减法:

[1,2,3]: ((0 - 3) - 2) - 1 = -6

当乘以时,它负责第二个错误的解决方案:

[1,2,3]: ((0 * 3) * 2) * 1 = 0

您的代码还存在其他一些问题(请参阅@ lurker的评论),但是,我不会详细介绍。相反,我建议一个与指定生产规则密切相关的谓词。由于语法是描述表达式而你想知道相应的值,我们称之为expr_val / 2。现在让我们自上而下地描述一个表达式:它可以是一个数字:

expr_val(X,X) :-
   number(X).

它可以是任意长的加法或减法或乘法序列。由于上述原因,所有三个序列都应以左关联方式进行评估。因此,对所有这些规则使用一条规则很诱人:

expr_val([Op|Es],V) :-
   sequenceoperator(Op),      % Op is one of the 3 operations
   exprseq_op_val(Es,Op,V).   % V is the result of a sequence of Ops

power函数是一个包含三个元素的列表,第一个是^,其他是表达式。所以这条规则很简单:

expr_val([^,E1,E2],V) :-
   expr_val(E1,V1),
   expr_val(E2,V2),
   V is V1^V2.

正弦和余弦的表达式都是包含两个元素的列表,第一个是sincos,第二个是表达式。请注意,sincos的参数是以弧度表示的角度。如果列表的第二个参数以弧度表示角度,则可以像在代码中一样使用sin / 1和cos / 2。但是,如果以度为单位获得角度,则需要先将其转换为弧度。我以后一种情况为例,使用适合您应用程序的情况。

expr_val([sin,E],V) :-
   expr_val(E,V1),
   V is sin(V1*pi/180).       % radians = degrees*pi/180
expr_val([cos,E],V) :-
   expr_val(E,V1),
   V is cos(V1*pi/180).       % radians = degrees*pi/180

对于expr_val / 2的第二条规则,您需要定义三个可能的序列运算符:

sequenceoperator(+).
sequenceoperator(-).
sequenceoperator(*).

随后谓词exprseq_op_val / 3。由于已经从expr_val / 2中的列表中删除了前导运算符,因此列表必须根据您的规范至少包含两个元素。为了以左关联方式评估序列,列表头部的值作为累加器传递给另一个谓词exprseq_op_val_ / 4

exprseq_op_val([E1,E2|Es],Op,V) :-
   expr_val(E1,V1),
   exprseq_op_val_([E2|Es],Op,V,V1).

描述了实际评估。基本上有两种情况:如果列表为空,那么无论运算符如何,累加器都保存结果。否则列表至少有一个元素。在这种情况下,另一个谓词op_val_args / 4传递相应操作的结果(Acc1),然后将其作为累加器递归传递到exprseq_op_val_ / 4以及列表的尾部(Es) :

exprseq_op_val_([],_Op,V,V).
exprseq_op_val_([E1|Es],Op,V,Acc0) :-
   expr_val(E1,V1),
   op_val_args(Op,Acc1,Acc0,V1),
   exprseq_op_val_(Es,Op,V,Acc1).

最后你必须定义op_val_args / 4,这又是非常简单的:

op_val_args(+,V,V1,V2) :-
   V is V1+V2.
op_val_args(-,V,V1,V2) :-
   V is V1-V2.
op_val_args(*,V,V1,V2) :-
   V is V1*V2.

现在让我们看看它是如何工作的。首先是您的示例查询:

   ?- expr_val([*,1,[^,2,2],[*,2,[+,[sin,0],5]]],V).
V = 40.0 ? ;
no

根据您的规范,最简单的表达式是一个数字:

   ?- expr_val(-3.14,V).
V = -3.14 ? ;
no

空列表不是表达式:

   ?- expr_val([],V).
no

运营商+-*至少需要2个参数:

   ?- expr_val([-],V).
no
   ?- expr_val([+,1],V).
no
   ?- expr_val([*,1,2],V).
V = 2 ? ;
no
   ?- expr_val([-,1,2,3],V).
V = -4 ? ;
no

power函数有两个参数:

   ?- expr_val([^,1,2,3],V).
no
   ?- expr_val([^,2,3],V).
V = 8 ? ;
no
   ?- expr_val([^,2],V).
no
   ?- expr_val([^],V).
no

等等......