使用剪切而不是在Prolog编程中



我是Prolog编程的新手。所以我想知道以下内容。我知道在Prolog中使用Cut(!)。有人可以解释一下我,在Prolog中使用Not ,何时以及如何使用Not 以及如何重写以下内容而不使用Cut并避免回溯。(仅使用谓词)

choose (0,_,[]) :- !.
choose (N,[H|T], [H|R]) :- M is N-1, Choose (M,T,R).
choose(N, [_|T],R) :- Choose(N,T,R)

并向我解释如何重写以下内容(仅使用谓词)

chooseAll(N,L,Res) :-
chooseAll(N,L,[],Res).
chooseAll(N,L,Seen,Res):-
choose(N,L,R),
not(member(R,Seen)),
!,
chooseAll(N,L,[R|Seen],Res).
chooseAll(_,_,Res,Res).

not/1是一个具有一个参数的谓词:目标(在您的示例中member(R,Seen))。

它根据否定作为有限失效原理工作。这意味着not(P)成功了,因为Prolog找不到满足P的方法。换句话说,如果您直接查询Prolog以获取P,它将返回false。然而,如果Prolog与P进入无限循环,它也将在not(P)中永远循环。

另一方面,如果P成功(统一与否),not(P)将失败。

由于它只有在falseP时才成功,因此在not中不会进行统一。如果P成功,not(P)失败,因此所做的任何统一都将被撤消。

所以not(member(R,Seen))检查R是否不是Seen的成员。如果Seen是变量,则not(member(R,Seen))将失败。给定R变量,not(member(R,Seen))将成功,Seen不是空列表。

另一方面,切割!not/1无关。在您的示例中,它只是意味着如果成功not(member(R,Seen),将执行剪切,因此在这种情况下不会对最后一个chooseAll/1进行重做。

那么chooseAll/4谓词是如何工作的呢?它以空列表Seen调用。在第一条中,我们首先调用choose(N,L,R)它将数字组合生成为列表R。我们希望防止一次又一次地生成相同的列表,因此接下来使用not(member(R,Seen))调用来检查R是否已Seen。对于第一个结果,情况并非如此,因此我们削减了!以防止系统回溯并选择最后一个子句,接下来我们递归调用chooseAll,但R添加到Seen列表中。

在递归调用中,我们再次要求choose/3生成一个组合R并将其添加到看到的列表中。但现在它将生成与第一次相同的结果:choose/3将为每个调用以相同的顺序创建答案。因此,这意味着Prolog将回溯not(member(R,Seen))并要求choose/3生成下一个组合。该组合不会是Seen的一部分,因此not(member(R,Seen))会成功,因此我们将再次剪切并进行递归调用,并将R添加到Seen中。

这种情况将继续下去,直到choose/3完全筋疲力尽,无法提出尚未成为Seen一部分的组合R。在这种情况下,Prolog 将回溯chooseAll/4谓词,从而执行最后一个谓词,这将结果ResSeen列表统一起来。

然而,这是一种低效的方法:对于我们生成的每个答案,我们将再次调用谓词,直到它生成新的答案。因此,ISO 谓词是findall/3谓词。它将谓词的所有结果放在一个列表中(而不是以相反的顺序)。我们可以像这样使用它:

chooseAll(N,L,Res) :-
findall(R,choose(N,L,R),Res).

注意:正如not/1上的文档所指定的那样:

如果无法证明Goal则为真。保留仅用于兼容性。新代码应使用+/1.

所以你最好使用+ member(R,Seen)因为它更助记符。

请让我们退后一步,考虑一些比如何使用一个或两个特定谓词(如!/0not/1)更通用的问题。

首先,让我们修复一些语法问题,并让您的原始代码读取:

选择(0,_,[]) :- !. 选择(N,[H|T], [H|R]) :- M 是 N-1,选择(M,T,R)。 选择(N, [_|T],R) :- 选择(N,T,R)。 选择全部(N,L,Res) :- 选择全部(N,L,[],Res)。 选择全部(N,L,Seen,Res):- 选择(N,L,R), not(member(R,Seen)), !, 选择全部(N,L,[R|看到],Res)。 选择全部(_,_,Res,Res)。

从此开始,我应用以下两个小更改:

  • 我用(+)/1而不是not/1,因为(+)/1是ISOnot/1但不是。
  • 我不是(is)/2,而是使用 CLP(FD) 约束(#=)/2来宣传更新的语言结构,并明确表示在这种情况下我们只整数进行推理,而不是例如浮点数或其他类型的数字。将此视为保证额外的类型 安全性

我们得到这两个小的变化:

选择(0,_,[]) :- !. 选择(N,[H|T], [H|R]) :- M #= N-1, choose(M,T,R). 选择(N, [_|T],R) :- 选择(N,T,R)。 选择全部(N,L,Res) :- 选择全部(N,L,[],Res)。 选择全部(N,L,Seen,Res):- 选择(N,L,R), \+ 成员(R,Seen), !, 选择全部(N,L,[R|看到],Res)。 选择全部(_,_,Res,Res)。

现在让我们开始吧!我渴望通过问来尝试主要谓词:有哪些解决方案?

为了找出答案,我发布了所谓的最 一般查询,其中所有参数都是新变量:

?- 选择全部(X, Y, Z).X = 0, Z = [[]]。

什么?这个谓词只有一个解决方案,对吧?右?

应该不会。

所以,我想得到这些基本问题的更多答案。为了获得它们,我进行了以下其他更改:

  • 删除!/0.
  • 我用dif/2来说明两个术语是不同的
  • 我稍微重新排序了子句。
  • 我为那些仅在N大于 0 时才适用的子句添加约束N #> 0

因此,我们有:

选择(0,_,[])。 选择(N,[H|T], [H|R]) :- N #> 0, M #= N-1, choose(M,T,R). 选择(N, [_|T],R) :- N #> 0, choose(N,T,R). 选择全部(N,L,Res) :- 选择全部(N,L,[],Res)。 选择全部(_,_,Res,Res)。 选择全部(N,L,Seen,Res):- 选择(N,L,R), maplist(dif(R), Seen), 选择全部(N,L,[R|看到],Res)。

现在我们有例如:

?- 选择全部(X, Y, Z). Z = [] ; X = 0, Z = [[]] ; X = 1, Y = [_1966|_1968], Z = [[_1966]] ; X = 1, Y = [_3214, _3220|_3222], Z = [[_3220], [_3214]], dif(_3220, _3214) ; X = 1, Y = [_3560, _3566, _3572|_3574], Z = [[_3572], [_3566], [_3560]], dif(_3572, _3560), dif(_3572, _3566), 迪夫(_3566, _3560) . X = 0, Z = [[]]。

我把它留作一个练习,以确定这个程序现在是否太笼统,太具体或两者兼而有之,并添加或删除必要的约束获得所需的答案。

我想展示的要点是,坚持纯谓词可以帮助你获得更通用的程序。使用!/0(+)/1对此无济于 事。为了避免在特定情况下回溯,同时保留谓词的通用性,例如使用新的谓词 if_/3

最新更新