Prolog:递归函数分支& “回归”

时间:2014-07-27 07:18:40

标签: list recursion prolog

我写了几个递归的Prolog谓词,并且遇到了一个我目前还不太了解的观点。

例如,我编写了谓词split/3,它将整数列表分为非负整数列表和负整数列表:

版本1

split([], [], []).
split([ListHead|ListTail], [ListHead|PList], NList) :- 
   ListHead >= 0,
   split(ListTail, PList, NList).
split([ListHead|ListTail], PList, [ListHead|NList]) :- 
   ListHead < 0,
   split(ListTail, PList, NList).

但在达成该解决方案之前,我在下面编写了解决方案,并想知道为什么它不能正常工作:

版本2

split([], [], []).
split([ListHead|ListTail], PList, NList) :- 
   ListHead >= 0,
   split(ListTail, [ListHead|PList], NList).
split([ListHead|ListTail], PList, NList) :- 
   ListHead < 0,
   split(ListTail, PList, [ListHead|NList]).

其中:

  • 第一个给定的参数分为ListHeadListTail
  • 如果ListHead元素(整数)大于或等于0,则它会添加到非负整数列表中,并使用参数进行递归调用,并使用未处理的Nlist
  • 如果ListHead元素(整数)小于0,则它会添加到负整数列表中,并用作带有未处理PList的递归调用的参数。

我不明白为什么版本2不起作用;它编译时没有任何警告,但只返回错误。与上述版本的唯一区别在于,整数元素前置NlistPList是在谓词定义(:-运算符之后)内完成的,而不是在{{1}}运算符之后谓词调用。对我来说,将结果作为下一次调用的参数的一部分进行前置是有意义的......

我觉得我错过了Prolog以“搜索”递归电话的方式!

有人可以解释为什么版本1按预期工作而版本2没有?

3 个答案:

答案 0 :(得分:0)

它不起作用,因为当你回到后退时,你正在失去元素。

在版本1中,PList在返回开始时用[]实例化,你开始堆叠这样的元素:

[ListHead|PList]    the same as   [ListHead| [] ] at first level.

最后你有所有的清单。

在版本2中,Plist仍然没有实例化,切割条件永远不会满足,因为你有类似的东西:

[[]|1,2,3,4,5,6]

并且不与任何东西匹配。

在版本2中,您需要在最后使用累加器(或辅助变量)将累加器复制到实际变量中,如下所示:

split([],A,B,A,B).
split([ListHead|ListTail], PList, NList, PListAcum, NListAcum) :- ListHead >= 0, 
    split(ListTail, PList, NList, [ListHead|PListAcum], NListAcum).
split([ListHead|ListTail], PList, NList, PListAcum, NListAcum) :- ListHead < 0, 
    split(ListTail, PList, NList,  PListAcum, [ListHead|NListAcum]).

你这样称呼:

split([1,2,3,-1,-2,-3] , P, N, [], []);

让我解释一下,累加器已初始化并累积数据。当列表为空时,第一行只将累加器复制到实变量中然后累加器将其元素丢失回y递归(如果你查看不同回溯级别上变量的名称,你将会理解)但真实的变量通过回溯保持不变。

你需要为你想要返回的每个变量都有一个累加器,或者像你的第一个版本一样。

您可以搜索有关累加器的信息。

电贺。

答案 1 :(得分:0)

模式匹配它是Prolog的一个关键特性,占该语言功能的一半,并且一起进行回溯,允许以优雅但不寻常的方式表达控制。以下是您可以“更正”第二个版本的方法

split([],[],[]).
split([ListHead|ListTail], PList, NList) :-
    ListHead >= 0, split(ListTail, Temp, NList), PList=[ListHead|Temp].
split([ListHead|ListTail], PList, NList) :-
    ListHead < 0, split(ListTail, PList, Temp), NList=[ListHead|Temp].

当然问题是基本情况无法与原始版本匹配。

答案 2 :(得分:0)

使用较短的变量名重写代码并将规则头分配移入规则体,可能更容易阅读。

我们假设它被一个给定的列表调用,产生它的两个半部分,其中一个非负数(我们称之为“正数”,简称为0,但是包括0),另一个,它的消极因素。

版本1 读取,

split([],[],[]).
split(X, Y, Z) :-        X = [A|B], Y = [A|POS], Z = NEG,
    A >= 0, split(B, POS, NEG).
split(X, Y, Z) :-        X = [A|B], Y = POS, Z = [A|NEG],
    A <  0, split(B, POS, NEG).
  • 一个空列表分成两个空列表;
  • 要将列表X与头部A拆分为非负数,我们将其尾部B拆分为两个列表,正面列表POS和否定列表NEG,并且(自然地)将A置于B的正面,以X的正面值返回;
  • 要将列表X与头部A拆分为负数,我们将其尾部B拆分为两个列表,积极列表POS和否定列表{{ 1}},并且(自然地)将NEG添加到A的否定内容,作为B的否定值返回。

版本2 读取,

X
  • 一个空列表分成两个空列表;
  • 要将列表split([],[],[]). split(X, Y, Z) :- X = [A|B], Y = POS, Z = NEG, A >= 0, split(B, [A|POS], NEG). split(X, Y, Z) :- X = [A|B], Y = POS, Z = NEG, A < 0, split(B, POS, [A|NEG]). 与头部X拆分为非负数,我们将其尾部A拆分为两个列表,正面列表和否定列表,我们要求 B的正面负责人与B最不可能)相同;然后我们只返回A的正面尾部(即B)作为POS的正面(即一个元素更短...... ?? );
  • 与负头元素类似。

我认为你可以看出这没有任何意义。

此处没有回溯,因为所有规则的子句都是互斥的(保证在回溯时失败)。