8

首先,我已经阅读了关于在 Prolog 中使用 cut 的所有其他帖子,并且肯定看到了与使用它们相关的问题。但是,对我来说仍然存在一些不确定性,我想一劳永逸地解决这个问题。

在下面的简单示例中,我们递归地遍历列表并检查每个第二个元素是否等于 1。这样做时,递归过程可能会以以下任一基本情况结束:空列表或具有单个元素的列表仍然存在。

base([]).
base([_]).
base([_,H|T]) :- H =:= 1, base(T).

执行时:

?- time(base([1])).
   % 0 inferences, 0.000 CPU in 0.000 seconds (74% CPU, 0 Lips)
   true ;
   % 2 inferences, 0.000 CPU in 0.000 seconds (83% CPU, 99502 Lips)
   false.

?- time(base([3,1,3])).
   % 2 inferences, 0.000 CPU in 0.000 seconds (79% CPU, 304044 Lips)
   true ;
   % 2 inferences, 0.000 CPU in 0.000 seconds (84% CPU, 122632 Lips)
   false.

在这种情况下,我总是在第二种基本情况(即表示列表中剩下的一个元素的那个)中使用显式切割运算符,如下所示,以消除冗余选择点。

base([]).
base([_]) :- !.
base([_,H|T]) :- H =:= 1, base(T).

现在我们得到:

?- time(base([1])).
   % 1 inferences, 0.000 CPU in 0.000 seconds (81% CPU, 49419 Lips)
   true.

?- time(base([3,1,3])).
   % 3 inferences, 0.000 CPU in 0.000 seconds (83% CPU, 388500 Lips)
   true.

我了解此切割的行为特定于规则的位置,可以被视为不好的做法。

然而,继续前进,可以将案例重新定位如下:

base([_,H|T]) :- H =:= 1, base(T).
base([_]).
base([]).

这也将在不使用切割的情况下消除冗余选择点,但当然,我们只需将选择点转移到具有偶数位数的列表的查询,如下所示:

?- time(base([3,1])).
% 2 inferences, 0.000 CPU in 0.000 seconds (82% CPU, 99157 Lips)
true ;
% 2 inferences, 0.000 CPU in 0.000 seconds (85% CPU, 96632 Lips)
false.

所以这显然也不是解决办法。但是,我们可以通过以下方式调整此规则顺序:

base([_,H|T]) :- H =:= 1, base(T), !.
base([_]).
base([]).

因为这实际上不会留下选择点。查看一些查询:

?- time(base([3])).
% 1 inferences, 0.000 CPU in 0.000 seconds (81% CPU, 157679 Lips)
true.

?- time(base([3,1])).
% 3 inferences, 0.000 CPU in 0.000 seconds (83% CPU, 138447 Lips)
true.

?- time(base([3,1,3])).
% 3 inferences, 0.000 CPU in 0.000 seconds (82% CPU, 393649 Lips)
true.

然而,再一次,由于规则的排序,这种切割的行为只能正常工作。如果有人将基本案例重新定位回原始形式,如下所示:

base([]).
base([_]).
base([_,H|T]) :- H =:= 1, base(T), !.

我们仍然会得到不需要的行为:

?- time(base([1])).
   % 0 inferences, 0.000 CPU in 0.000 seconds (83% CPU, 0 Lips)
   true ;
   % 2 inferences, 0.000 CPU in 0.000 seconds (84% CPU, 119546 Lips)
   false.

在这种情况下,我总是在第二个基本情况中使用单切,因为我是唯一一个经历过我的代码的人,而且我已经习惯了。但是,我在另一篇 SO 帖子上的一个答案中被告知,不建议使用 cut 运算符,我应该尽量避免使用它。

这让我想到了我的双向问题:

  • 如果一个剪切,无论它存在的规则的位置如何,都改变了行为,但没有改变解决方案(如上面的例子),它仍然被认为是不好的做法吗?

  • 如果我想取消上面示例中的典型冗余选择点以使谓词完全确定,是否有其他推荐的方法来完成此操作而不是使用削减?

提前致谢!

4

2 回答 2

7

总是尽量避免!/0。几乎总是!/0完全破坏程序的声明性语义。

凡是能用模式匹配表达的东西,都应该用模式匹配来表达。在您的示例中:

每一秒([])。
每秒([_|Ls]):-
        每_秒_(Ls)。

每一秒_([])。
every_second_([1|Rest]) :- every_second(Rest)。

就像在您的不纯版本中一样,您发布的示例没有任何选择点:

?- 每秒([1])。
真的。

?- 每秒([3,1])。
真的。

?- 每秒([3,1,3])。
真的。

还要注意,在这个版本中,所有谓词都是完全纯的,并且可以在所有方向上使用。该关系也适用于最一般的查询并生成答案,就像我们对逻辑关系所期望的那样:

?- 每秒(Ls)。
LS = [] ;
LS = [_G774] ;
Ls = [_G774, 1] ;
Ls = [_G774, 1, _G780] ;
Ls = [_G774, 1, _G780, 1]。

由于您使用的不纯或非声明性谓词 ( !/0, (=:=)/2),您发布的所有版本都无法做到这一点!

在推理列表时,您几乎总是可以单独使用模式匹配来区分情况。如果这是不可能的,例如if_/3使用逻辑纯度,同时仍保持可接受的性能。

于 2016-06-12T16:24:27.167 回答
2

诀窍是“currying”规则中的无限数量:

base([]). 
base([_|Q]) :- base2(Q).

base2([]). 
base2([H|Q]) :- H =:= 1, base(Q).

然而,说削减是坏的,这是一个坏规则。事实上,我最喜欢的是:

base([]) :- !. 
base([_]) :- !. 
base([_,H|Q]) :- !, H =:= 1, base(Q).

关于这个例子primes(++)

primes([5]).
primes([7]).
primes([11]).

对比

primes([5]) :- !.
primes([7]) :- !.
primes([11]) :- !.
于 2016-06-12T16:04:53.133 回答