Prolog程序:定义仿函数的另一种方法

时间:2014-11-14 20:34:02

标签: prolog prolog-cut

在我上次的考试中,我必须根据以下说明编写一个名为double/2的Prolog谓词:如果double(X, Y)是相同长度的整数列表,则Y应该为真X的{​​{1}},其中每个偶数X都被其双倍替换。所以,例如,查询    double([1, 3, 2, 4], X). 应该结果     X = [1, 3, 4, 8].

我被允许简单地使用仿函数even/1,而没有定义它(实际上很容易定义它),当参数是偶数时这是真的,否则是假的。实际上我最后还使用了仿函数odd/1来编写程序。但是我的教授对我说:"你本可以只使用它来编写它,它也没有必要使用也奇怪!"所以我想知道我怎么能这样写它。

我写的内容如下:

double([], []).
double([N|L], [N|D]) :- odd(N), !, double(L, D).
double([N|L], [M|D]) :- even(N), M is 2*N, double(L, D).

备注:如果我从最后一行代码中删除even(N)(因此,如果我仅使用odd(N),这实际上与仅使用even(N)相同,因为我只使用了一个{{1}}他们),然后程序仍然有效。但这不是一个理想的解决方案,因为这种方式可以解决问题。变红了(在我的程序中它是绿色切割)。

2 个答案:

答案 0 :(得分:4)

您可以使用标准的 if-then-else 控件构造:

double([], []).
double([N|L], [M|D]) :-
    (   even(N) ->
        M is 2*N
    ;   M is N
    ),
    double(L, D).

这个替代解决方案的性能优势(除了避免在整数不奇怪时重复计算)是,假设您的Prolog系统实现了第一个参数索引(大多数情况下),double/2的正确子句在每次调用时总是会选择谓词而不创建一个选择点(假设在第一个参数被实例化的情况下调用谓词但是这个或你的定义定义不起作用)。请注意,在第一个子句中,第一个参数是一个原子(空列表),而在第二个子句中,参数是一个(非空)列表。第一个参数索引用于避免在不能用于解析当前目标的证明期间尝试子句。

答案 1 :(得分:4)

如果您只是对正确的解决方案感兴趣,请选择@ PauloMoura的解决方案。以下是我认为本练习的目的。采取原始程序,似乎(乍一看),可以删除第二个子句中的目标even(N)

但在此之前,让我明确指出谓词名doubles/2是用词不当。我宁愿说list_semidoubled/2 ......

odd(N) :-
   N mod 2 =:= 1.

double([], []).
double([N|L], [N|D]) :-
   odd(N), !, double(L, D).
double([N|L], [M|D]) :-
   % even(N),
   M is 2*N, double(L, D).

然而,上述削减比我们预期的要多一点。当odd(N)单独存在时它不会被切断,但是有一小部分额外条件潜入我们的程序中。你看到了吗?让我们来看看相关部分:

double([N|L], [N|D]) :-
   odd(N), !, ...

odd(N),但请看上面!在头部另一个条件是在那里。它一直等到它可以造成严重破坏! "隐藏"条件是:

  

如果N与第二个参数中的第一个元素相等(统一)!

让我们试一试!

?- double([1], Xs).
Xs = [1].

Worx如预期的那样,不是。但是:

?- double([1], [2]).
true.

天狮,这里发生了什么?那应该会失败!

以这种方式行事的谓词缺乏坚定性。我们希望查询失败,因为Prolog没有向我们展示这个解决方案。

所以上面的切割并不总是像你预期的那样削减,而是略低于此。这些错误通常很难找到,因此从一开始就避免这些错误是个好主意。您有几种选择:

1坚持纯粹,单调的Prolog

这对初学者来说可能是最好的。并不一定效率低下。我宁愿:

double(Xs, Ys) :
   maplist(n_semidoubled, Xs, Ys).

n_semidoubled(X, Y) :-
   M is X mod 2,
   (  M = 0,
      Y is X*2
   ;  M = 1,
      Y is X
   ).

这可以改进 - 稍微有些黑客:

 n_semidoubled(X, Y) :-
    Y is X*(2-X mod 2).

2使用(\+)/1once/1,if-then-else

@PaulMoura已经向你展示了这种可能性。另一种方法是使用(\+)/1

n_semidoubled(X, X) :-
   odd(X).
n_semidoubled(X, Y) :-
   \+odd(X),
   Y is X*2.

3缩小裁剪范围

如果您现在仍然决定使用此构造,请尝试重新构建程序,以使裁剪的范围尽可能本地化。也就是说,不是将剪切放在递归规则中,而是创建一个本地谓词:

doubles([], []).
doubles([E|Es], [M|Ms]) :-
   n_semidoubled(E, M),
   doubles(Es, Ms).

n_semidoubled(E, M) :-
   odd(E),
   !,
   M is E.
n_semidoubled(E, M) :-
   M is E*2.

您的原始程序中存在另一个与切割问题无关的异常现象。考虑:

?- double([1*1],Xs).
   Xs = [1*1].