3

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

为了简单起见,我被允许使用 functoreven/1而不定义它(实际上它很容易定义它),当参数为偶数时为真,否则为假。实际上,我最终也使用 functor 编写了程序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)从最后一行代码中删除(所以如果我使用 only odd(N),这实际上与 using only 相同even(N),因为我只使用其中一个),那么程序仍然可以工作。但这不是一个理想的解决方案,因为这样“剪切”变成了红色剪切(在我的程序中它是绿色剪切)。

4

2 回答 2

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谓词的正确子句将始终在每次调用时被选中不创建选择点(假设谓词是在第一个参数实例化的情况下调用的,但是这个或您的定义定义将无法正常工作)。请注意,在第一个子句中,第一个参数是一个原子(空列表),而在第二个子句中,参数是一个(非空)列表。第一个参数索引用于避免在证明期间尝试无法解决当前目标的子句。

于 2014-11-14T21:09:46.553 回答
4

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

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

奇数(N):-
   N mod 2 =:= 1。

双倍的([], [])。
双([N|L],[N|D]):-
   奇数(N),!,双(L,D)。
双([N|L],[M|D]):-
   %偶数(N),
   M 是 2*N,双 (L, D)。

然而,上面的削减比我们预期的要多一点。当单独为真时,它并没有削减odd(N),但是有一个很小的额外条件潜入了我们的程序。你看到了吗?让我们看一下相关部分:

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

odd(N),但请看上面!在头脑中,另一种情况正在那里。它一直等到它可以造成严重破坏!“隐藏”的条件是:

IfN等于(统一)第二个参数中的第一个元素!

让我们试试吧!

?- 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 使用(\+)/1, once/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].
于 2014-11-14T21:53:52.443 回答