【问题标题】:Using Cut and Not in Prolog Programming在 Prolog 编程中使用 Cut 和 Not
【发布时间】:2017-08-04 18:38:34
【问题描述】:

我是 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).

【问题讨论】:

  • Nitpick:您是 Prolog 编程的新手,而不是“新奇”。 Prolog 对您来说可能是“新颖的”,但绝不会反过来。 :)
  • choose (0,_,[]) :- !. 是无效的语法。谓词名称和后面的左括号之间不能有空格。

标签: prolog prolog-cut


【解决方案1】:

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

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

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

因为只有Pfalse才成功,所以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 上的documentation 指定:

如果无法证明Goal,则为真。 仅出于兼容性目的而保留。新代码应使用\+/1

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

【讨论】:

  • 我会鼓励人们使用\+/1 而不是not/1。另外,您的意思是第 2 段中的无限循环。否则,答案很惊人!
【解决方案2】:

请让我们退后一步,考虑一些比如何使用诸如!/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), 不是(成员(R,Seen)), !, 全选(N,L,[R|Seen],Res)。 全选(_,_,Re​​s,Res)。

由此开始,我应用以下两个小改动:

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

我们得到这两个小变化:

选择(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), \+ 成员(R,Seen), !, 全选(N,L,[R|Seen],Res)。 全选(_,_,Re​​s,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,选择(M,T,R)。 选择(N,[_|T],R):- N#> 0,选择(N,T,R)。 全选(N,L,Res):- 全选(N,L,[],Res)。 全选(_,_,Re​​s,Res)。 全选(N,L,Seen,Res):- 选择(N,L,R), maplist(dif(R), Seen), 全选(N,L,[R|Seen],Res)。

现在我们有例如:

?- 全选(X, Y, Z)。 Z = []; X = 0, Z = [[]] ; X = 1, Y = [_1966|_1968], Z = [[_1966]] ; X = 1, Y = [_3214, _3220|_3222], Z = [[_3220],[_3214]], 差异(_3220,_3214); X = 1, Y = [_3560, _3566, _3572|_3574], Z = [[_3572],[_3566],[_3560]], 差异(_3572,_3560), 差异(_3572,_3566), 差异(_3566,_3560)。 X = 0, Z = [[]]。

我把它作为一个练习来确定这个程序现在是否过于笼统、过于具体或两者兼而有之,并添加或删除必要的 约束 以获取 only 想要的答案。

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

【讨论】:

    猜你喜欢
    • 2020-09-14
    • 2013-01-10
    • 2015-12-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-04
    • 1970-01-01
    相关资源
    最近更新 更多