首先,我已经阅读了有关Prolog削减使用情况的所有其他帖子,并且肯定会看到与使用它们相关的问题。但是,对我来说仍然存在一些不明智之处,我想一劳永逸地解决这个问题。
在下面的简单示例中,我们递归遍历列表并检查每个第2个元素是否等于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操作符,我应该尽可能地避免使用它。
这让我想到了我的双方问题:
如果剪切,无论其存在的规则的位置如何,都会更改行为,而不会更改解决方案(如示例中所示)以上),它仍然被认为是不好的做法?
如果我想取消上面示例中的典型冗余选择点以使谓词完全确定,那么是否有其他推荐的方法来实现此目的而不是使用剪切
提前致谢!
答案 0 :(得分:7)
始终尽量避免!/0
。几乎无一例外,!/0
完全破坏了程序的声明性语义。
可以表达的所有内容都可以通过模式匹配来表示 。在您的示例中:
every_second([]). every_second([_|Ls]) :- every_second_(Ls). every_second_([]). every_second_([1|Rest]) :- every_second(Rest).
就像你的不纯版本一样,你发布的例子没有任何选择:
?- every_second([1]). true. ?- every_second([3,1]). true. ?- every_second([3,1,3]). true.
另请注意,在此版本中,所有谓词都是纯粹且可在所有方向上使用 。该关系也适用于最常规的查询和生成答案,正如我们对逻辑关系所期望的那样:
?- every_second(Ls). Ls = [] ; Ls = [_G774] ; Ls = [_G774, 1] ; Ls = [_G774, 1, _G780] ; Ls = [_G774, 1, _G780, 1] .
由于您使用的不纯或非声明性谓词(!/0
,(=:=)/2
),您发布的所有版本均无法执行此操作!
在对列表进行推理时,您几乎总是可以单独使用模式匹配来区分这些情况。如果无法做到这一点,请使用例如if_/3
表示逻辑纯度,同时仍保持可接受的性能。
答案 1 :(得分:2)
诀窍在于对规则中的未对数量进行“调整”:
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]).
VS
primes([5]) :- !.
primes([7]) :- !.
primes([11]) :- !.