【问题标题】:Defining a mathematical language in prolog在 prolog 中定义数学语言
【发布时间】:2017-05-14 11:50:04
【问题描述】:

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

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”,该函数接受一个列表,该列表是该语言编写的表达式,以及一个变量,该变量是与该语言对应的数值。 示例:

?-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).

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

【问题讨论】:

  • 如果check(L) 在列表中失败,整个谓词不应该简单地失败而不是尝试评估无效列表吗?并且not(is_list(Ls)) 可能在语义上不合理,因为在这种情况下,列表的尾部(在这种情况下为Ls)预计始终是一个列表。
  • 我不会以这种方式组织程序,但鉴于您的方法,您需要做的是让每个运算符谓词在调用 d/2 时递归并随时调用 d/2它遇到一个子列表(您目前只允许not(is_list(L)))。

标签: prolog


【解决方案1】:

您使用的规范看起来像一个生产规则,描述 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 的 cmets),但是,我不会对此进行进一步详细说明。相反,我建议使用一个严格遵守指定生产规则的谓词。由于语法是描述表达式,你想知道对应的值,我们称之为 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

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

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

幂函数正好有两个参数:

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

等等……

【讨论】:

    猜你喜欢
    • 2013-08-27
    • 1970-01-01
    • 1970-01-01
    • 2014-03-12
    • 2023-03-13
    • 1970-01-01
    • 2010-11-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多