如果您只是对正确的解决方案感兴趣,请采用@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].